嵌入式linux:linux select函数与socket

2019-07-13 01:01发布

转自:http://blog.csdn.net/lingfengtengfei/article/details/12392449
Linux中,我们可以使用select函数实现I/O端口的复用,传递给 select函数的参数会告诉内核:       •我们所关心的文件描述符       •对每个描述符,我们所关心的状态。(我们是要想从一个文件描述符中读或者写,还是关注一个描述符中是否出现异常)       •我们要等待多长时间。(我们可以等待无限长的时间,等待固定的一段时间,或者根本就不等待)     select函数返回后,内核告诉我们一下信息:       •对我们的要求已经做好准备的描述符的个数       •对于三种条件哪些描述符已经做好准备.(读,写,异常)    有了这些返回信息,我们可以调用合适的I/O函数(通常是 read write),并且这些函数不会再阻塞.   #include         int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);        返回:做好准备的文件描述符的个数,超时为0,错误为 -1.        首先我们先看一下最后一个参数。它指明我们要等待的时间: struct timeval{               long tv_sec;   /* */         long tv_usec;  /*微秒 */        }        有三种情况:     timeout == NULL  等待无限长的时间。等待可以被一个信号中断。当有一个描述符做好准备或者是捕获到一个信号时函数会返回。如果捕获到一个信号, select函数将返回 -1,并将变量 erro设为 EINTR     timeout->tv_sec == 0 &&timeout->tv_usec == 0不等待,直接返回。加入描述符集的描述符都会被测试,并且返回满足要求的描述符的个数。这种方法通过轮询,无阻塞地获得了多个文件描述符状态。     timeout->tv_sec !=0 ||timeout->tv_usec!= 0 等待指定的时间。当有描述符符合条件或者超过超时时间的话,函数返回。在超时时间即将用完但又没有描述符合条件的话,返回 0。对于第一种情况,等待也会被信号所中断。        中间的三个参数 readset, writset, exceptset,指向描述符集。这些参数指明了我们关心哪些描述符,和需要满足什么条件(可写,可读,异常)。一个文件描述集保存在 fd_set 类型中。fd_set类型变量每一位代表了一个描述符。我们也可以认为它只是一个由很多二进制位构成的数组。如下图所示:        对于 fd_set类型的变量我们所能做的就是声明一个变量,为变量赋一个同种类型变量的值,或者使用以下几个宏来控制它:   #include     int FD_ZERO(int fd, fd_set *fdset);    int FD_CLR(int fd, fd_set *fdset);    int FD_SET(int fd, fd_set *fd_set);    int FD_ISSET(int fd, fd_set *fdset);     FD_ZERO宏将一个 fd_set类型变量的所有位都设为 0,使用FD_SET将变量的某个位置位。清除某个位时可以使用 FD_CLR,我们可以使用FD_ISSET来测试某个位是否被置位。    当声明了一个文件描述符集后,必须用FD_ZERO将所有位置零。之后将我们所感兴趣的描述符所对应的位置位,操作如下:   fd_set rset;    int fd;    FD_ZERO(&rset);    FD_SET(fd, &rset);    FD_SET(stdin, &rset);          select返回后,用FD_ISSET测试给定位是否置位:   if(FD_ISSET(fd, &rset)    { ... } 具体解释select的参数: 1intmaxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错。 说明:对于这个原理的解释可以看上边fd_set的详细解释,fd_set是以位图的形式来存储这些文件描述符。maxfdp也就是定义了位图中有效的位的个数。 2fd_set*readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读;如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 3fd_set*writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 4fd_set*errorfds同上面两个参数的意图,用来监视文件错误异常文件。 5structtimeval* timeoutselect的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为00毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即 selecttimeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 说明: 函数返回: 1)当监视的相应的文件描述符集中满足条件时,比如说读文件描述符集中有数据到来时,内核(I/O)根据状态修改文件描述符集,并返回一个大于0的数。某些文件可读写或出错
2)当没有满足条件的文件描述符,且设置的timeval监控时间超时时,select函数会返回一个为0的值。等待超时,没有可读写或错误的文件 3)当select返回负值时,发生错误。 这里说每次轮询调用select函数都要FD_ZERO(&fds)清空集合,否则不能检测描述符变化。为什么?

  while(1) 
  { 
       FD_ZERO(&fds); //每次循环都要清空集合,否则不能检测描述符变化
       FD_SET(sock,&fds); //添加描述符 
       FD_SET(fp,&fds); //同上
       maxfdp=sock>fp?sock+1:fp+1; //描述符最大值加1
       switch(select(maxfdp,&fds,&fds,NULL,&timeout)) //select使用 
       { 
          case -1: exit(-1);break; //select错误,退出程序 
          case 0:break; //再次轮询
          default: 
            //......
          }
select 返回后,它会修改每个fd_set结构,删除那些不存在激活状态的I/O操作的套接字句柄。所以你每次调用select之前,必须初始化一下。 这是一个输入输出参数,如果你只在循环外初始化一次,select函数会改变这个结构,最后的例子中使用了文件描述符备份,这样就不用每次都初始化一次了。
理解select模型: 理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8fd 1)执行fd_set set;FD_ZERO(&set);set用位表示是0000,0000 2)若fd5,执行FD_SET(fd,&set);set变为0001,0000(5位置为1) 3)若再加入fd2fd=1,set变为0001,0011 4)执行select(6,&set,0,0,0)阻塞等待 5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。 基于上面的讨论,可以轻松得出select模型的特点: 1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。 2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。 3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环arrayFD_ISSET判断是否有时间发生)。 基本原理 Resize icon  select()系统调用代码走读 调用顺序如下:sys_select() à core_sys_select() à do_select() à fop->poll()
特殊情况比如对方通过一个socket句柄发来了紧急数据。如果我们程序里只想检测某个socket是否有数据可读,我们可以这样:fd_set rdfds; /* 先申明一个 fd_set 集合来保存我们要检测的 socket句柄 */
struct timeval tv; /* 申明一个时间变量来保存时间 */
int ret; /* 保存返回值 */
FD_ZERO(&rdfds); /* 用select函数之前先把集合清零 */
FD_SET(socket, &rdfds); /* 把要检测的句柄socket加入到集合里 */
tv.tv_sec = 1;
tv.tv_usec = 500; /* 设置select等待的最大时间为1秒加500毫秒 */
ret = select(socket + 1, &rdfds, NULL, NULL, &tv); /* 检测我们上面设置到集合rdfds里的句柄是否有可读信息 */
if(ret < 0) perror("select");/* 这说明select函数出错 */
else if(ret == 0) printf("超时 "); /* 说明在我们设定的时间值1秒加500毫秒的时间内,socket的状态没有发生变化 */
else { /* 说明等待时间还未到1秒加500毫秒,socket的状态发生了变化 */
    printf("ret=%d ", ret); /* ret这个返回值记录了发生状态变化的句柄的数目,由于我们只监视了socket这一个句柄,所以这里一定ret=1,如果同时有多个句柄发生变化返回的就是句柄的总和了 */
    /* 这里我们就应该从socket这个句柄里读取数据了,因为select函数已经告诉我们这个句柄里有数据可读 */
    if(FD_ISSET(socket, &rdfds)) { /* 先判断一下socket这外被监视的句柄是否真的变成可读的了 */
        /* 读取socket句柄里的数据 */
        recv(...);
    }
}
   注意select函数的第一个参数,是所有加入集合的句柄值的最大那个值还要加1

下面是一个TCP服务器的例子 main.c #include #include #include        //open() close() #include #include #include    //read()  wrie() #include #include #include #include #include #include "MySocket.h" #define DEVICE_NAME "/dev/myspidev" #define TXRXFIFO 10 //在驱动中定义的FIFO大小是10.实际上FIFO始终是64.但是在驱动中的读取函数实现的是超过10时读取10个字节。 #define SECOND 1 #define USECOND 0//定义select时间长度 int SocketListen; int SocketConnect; struct sockaddr_in server_addr; struct sockaddr_in client_addr; char SocketTxBuf[SOCKETBUFSIZE]; char SocketRxBuf[SOCKETBUFSIZE]; int main() {     int fd,maxfd;     int i=0;     fd_set rset, allset;        //select所需的文件描述符集合     struct timeval timeout;     int nready;     int sin_size;               //地址信息结构体大小     ssize_t SocketRxlen; //tcp接受到的数据长度     char TxBuf[TXRXFIFO]={1,2,3,4,5,6,7,8,9,10};     char RxBuf[TXRXFIFO];     unsigned char j=0;     printf("My2416 SPI dev test! ");     fd=open(DEVICE_NAME,O_RDWR);     printf("SPIfd=%d ",fd);     if(fd==-1)     {         printf("open device %s error ",DEVICE_NAME);     }     else     {         SocketInit();         //初始化select         maxfd = SocketListen;         FD_ZERO(&allset);           //清空         FD_SET(SocketListen, &allset);  //将监听socket加入select检测的描述符集合         while(1)         {             // timeout setting             timeout.tv_sec = SECOND;             timeout.tv_usec = USECOND;             rset = allset;//文件描述符备份,避免每次进行初始化。             nready = select(maxfd + 1, &rset, NULL, NULL, &timeout);    //检测我们上面设置到集合rset里的句柄是否有可读信息             if(nready==-1)//select出错             {                 perror("select");             }             else if(nready==0)//select超时             {                 printf("Select outof time %ds%dus! ",SECOND,USECOND);             }             else if(nready>0)//在等待时间内socket文件描述符(SocketListen或SocketConnect)有变化 可读写或出错             {                 if (FD_ISSET(SocketListen, &rset))//先判断一下SocketListen这外被监视的句柄是否真的变成可读的了                 {                       //检测是否有新客户端请求                     printf("Accept a connection. ");                     //调用accept,返回服务器与客户端连接的socket描述符                     sin_size = sizeof(struct sockaddr_in);                     if ((SocketConnect = accept(SocketListen, (struct sockaddr *)&client_addr, (socklen_t *) & sin_size)) == -1)                     {                         perror("Accept() error ");                         continue;                     }                     FD_SET(SocketConnect, &allset); //将新socket连接放入select监听集合                     if (SocketConnect > maxfd)                     {                         maxfd = SocketConnect;  //确认maxfd是最大描述符                     }                 }                 // 有客户连接,检测是否有数据                 if (FD_ISSET(SocketConnect, &rset))                 {                     printf("Receive from connect SocketConnect[%d]. ", SocketConnect);                     if ((SocketRxlen = recv(SocketConnect, SocketRxBuf, SOCKETBUFSIZE, 0)) == 0)                     {               //从客户端socket读数据,等于0表示网络中断                         close(SocketConnect);  //关闭socket连接                         printf("SocketConnect closed. ");                         FD_CLR(SocketConnect, &allset);    //从监听集合中删除此socket连接                     }                     else                         process_client(&SocketConnect,SocketRxBuf, SocketRxlen); //接收到客户数据,开始处理                         memset(SocketRxBuf,0,strlen(SocketRxBuf));                 }             }             /*            write(fd,TxBuf,TXRXFIFO);            read(fd,RxBuf,TXRXFIFO);            printf("APP:TX date is %d %d %d %d ",TxBuf[0],TxBuf[1],TxBuf[2],TxBuf[3]);            printf("APP:RX date is %d %d %d %d ",RxBuf[0],RxBuf[1],RxBuf[2],RxBuf[3]);            */         }         close(SocketListen);         printf("******************** ");         printf("Close SpiApp succeed! ");     }     return 0; } socket.c #include #include #include #include #include #include #include #include #include #include #include "MySocket.h" #define PORT 3333 extern int SocketListen; extern int SocketConnect; extern struct sockaddr_in server_addr; extern struct sockaddr_in client_addr; extern char SocketTxBuf[SOCKETBUFSIZE]; extern char SocketRxBuf[SOCKETBUFSIZE]; void *thrd_write(void *arg)        //写线程 {     while(1)     {         if(strlen(SocketRxBuf)>0)         {             printf("SocketRxBuf len is %d ",strlen(SocketRxBuf));             if(send(SocketConnect,SocketRxBuf,strlen(SocketRxBuf),0)==-1)            //写功能             {                 printf("socket send Error ");                 close(SocketConnect);                 //exit(1);             }             memset(SocketRxBuf,0,strlen(SocketRxBuf));         }     }     pthread_exit(NULL);            //线程退出 } void SocketInit(void) {     int yes = 1;//允许重复使用本地地址与套接字进行绑定     SocketListen = socket(AF_INET,SOCK_STREAM,0);     if(SocketListen == -1)     {         perror("socket");         //printf("Socket error ");         exit(1);     }     printf("SocketListen fd=%d ",SocketListen);     if (setsockopt(SocketListen, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1)     {         perror("setsockopt");         exit(1);     }     bzero(&server_addr,sizeof(struct sockaddr_in));     server_addr.sin_family = AF_INET;//IPV4     server_addr.sin_addr.s_addr = htonl(INADDR_ANY);     server_addr.sin_port = htons(PORT);     int on=1;     setsockopt(SocketListen,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));     if(bind(SocketListen,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr_in))==-1)     {         perror("bind");         exit(1);         //exit(1);     }     if(listen(SocketListen,20)==-1)     {         perror("listen");         exit(1);         //exit(1);     }     printf("listen port %d ", PORT);     /*     int res2;     pid_t child;     int r_size;     socklen_t sin_size;     pthread_t thread_write;   //线程id,即线程标识符     while(1)     {         printf("listen port %d ", PORT);         memset(SocketTxBuf,0,100);         sin_size = sizeof(struct sockaddr_in);         if((SocketConnect=accept(sockfd,(struct sockaddr *)(&client_addr),&sin_size))==-1)         {             printf("Accept error ");             break;             //exit(1);         }         else         {             printf("Server get connection from %s ",inet_ntoa(client_addr.sin_addr));             res2=pthread_create(&thread_write,NULL,thrd_write,NULL);       //创建线程             if(res2!=0)             {                 printf("Pthread socket write create fail ");                 //exit(res2);             }             while(1)        //读取客户端信息             {                 //char SocketBuf[100];                 int r_size=0;                 if((r_size=recv(SocketConnect,SocketRxBuf,SOCKETBUFSIZE,0))==-1)                 {                     printf("Read error ");                     close(SocketConnect);                     break;                     //exit(1);                 }                 else if(r_size==0)//客户端主动断开链接                 {                     printf("client close succeed! ");                     close(SocketConnect);                     break;                 }                 else if(r_size>0)                 {                     printf("Server Received len %d::%s ",r_size,SocketRxBuf);                 }             }         }     }     close(sockfd);     */ } /************************************************* * Function    : process_client() * Description : 处理客户端连接函数 * Calls       : * Called By   : main() * Input       : * Output      : * Return      : *************************************************/ void process_client(int* socketcon ,char *recvbuf, int len) {     //char sendbuf[SOCKETBUFSIZE];     printf("Received client message: %s ", recvbuf);     if(send(*socketcon,recvbuf,len,0)==-1)     {         printf("socket send Error ");         close(*socketcon);                 //exit(1);     } }