一.概述:

系统提供select函数来实现I/O复用输入/输出模型。select系统调用是用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄中有一个或多个发生生了状态改变。




二.select函数:

以下为man文本中的解释:

 /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

nfds参数:需要监视的文件描述符集中最大的文件描述符 + 1;

readfds:输入/输出型参数,需要监视的可读文件描述符集合。

rwritefds:输入/输出型参数,需要监视的可写文件描述符集合。

exceptds:输入/输出型参数,需要监视的异常文件描述符集合。(一般为NULL)

timeout参数:输入/输出型参数,

NULL:则表示select()没有timeout,select将一直被阻塞,直到某个文件描述符上发生了事件。
0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
特定的时间值:如果在指定的时间段里没有事件发生,select将超时返回。

返回值:执行成功则返回文件描述符集状态已改变的个数:

                如果文件描述符集中没有满足条件的,并且时间超出了timeout,则返回0;

                出差返回-1,此时参数readfds,writefds,exceptfds和timeout的值变成不可预测。并置相应的错误码:

EBADF :文件描述词为无效的或该文件已关闭
EINTR: 此调用被信号所中断
EINVAL: 参数n 为负值。
ENOMEM :核心内存不足



struct timeval:结构用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0。

struct timeval结构体:一个常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数

struct timeval  
{  
    long tv_sec;    //second  
    long tv_usec;   //microsecond  
};

下面的宏提供了处理这三种描述符集的方式:
FD_CLR(inr fd,fd_set* set):用来清除描述词组set中相关fd 的位
FD_ISSET(int fd,fd_set *set):用来测试描述词组set中相关fd 的位是否为真(也就是是否已经就绪)
FD_SET(int fd,fd_set*set):用来设置描述词组set中相关fd的位
FD_ZERO(fd_set *set):用来清除描述词组set的全部位




三.fd_set理解:

理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,即fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set; FD_ZERO(&set),则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set),后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件
发生的fd=5被清空

PS:readfds,writefds,exceptfds,timeout都是输入/输出型参数,输入时,是你自己设置的值,输出时是改变后的值。




四.相关代码:

(1).监控标准输入输出:

 1 /****************************************                                                                                                 
  2     > File Name:test.c
  3     > Author:xiaoxiaohui
  4     > mail:1924224891@qq.com
  5     > Created Time:2016年05月23日 星期一 16时11分45秒
  6 ****************************************/
  7 
  8 #include<stdio.h>
  9 #include<stdlib.h>
 10 #include<sys/types.h>
 11 #include<sys/time.h>
 12 #include<unistd.h>
 13 #include<string.h>
 14 
 15 const int LEN = 1024;
 16 int fds[2];          //只监测标准输入与输出这两个文件描述符
 17 int main()
 18 {
 19 
 20     int std_in = 0;
 21     int std_out = 1;
 22     int fds_max = 1;
 23     fd_set reads, writes;
 24     struct timeval timeout;
 25 
 26     fds[0] = std_in;
 27     fds[1] = std_out;
 28 
 29     while(1)
 30     {
 31         FD_ZERO(&reads);
 32         FD_ZERO(&writes);
 33         FD_SET(std_in, &reads);          //标准输入关注的是读事件
 34         FD_SET(std_out, &writes);       //标准输出关注的是写事件
 35         timeout.tv_sec = 5;
 36         timeout.tv_usec = 0;
 37         switch( select(fds_max + 1, &reads, &writes, NULL, &timeout))
 38         {
 39             case 0:
 40                 printf("select time out ......\n");
 41                 break;
 42             case -1:
 43                 perror("select");
 44                 break;
 45             default:
 46                 if(FD_ISSET(fds[0], &reads))       //可以从标准输入中读
 47                 {
 48                     char buf[LEN];
 49                     memset(buf, '\0', LEN);
 50                     gets(buf);
 51                     printf("echo: %s\n", buf);      
 52 
 53                     if(strncmp(buf, "quit", 4) == 0)
 54                     {
 55                         exit(0);
 56                     }
 57                 }
 58                 if(FD_ISSET(fds[1], &writes))
 59                 {
 60                     char* buf = "write is ready.......\n";
 61                     printf("%s", buf);
 62                     sleep(5);
 63                 }
 64                 break;
 65         }
 66     }
 67 
 68 
 69 }

执行结果:

linux下select函数详解及实例_linux


(2).多路复用的TCP套接字编程:

server.c:

1 /****************************************                                                                                                 
  2     > File Name:server.c
  3     > Author:xiaoxiaohui
  4     > mail:1924224891@qq.com
  5     > Created Time:2016年05月23日 星期一 12时18分21秒
  6 ****************************************/
  7 
  8 #include<stdio.h>
  9 #include<stdlib.h>
 10 #include<sys/types.h>
 11 #include<sys/socket.h>
 12 #include<netinet/in.h>
 13 #include<arpa/inet.h>
 14 #include<string.h>
 15 #include<sys/time.h>
 16 #include<unistd.h>
 17 
 18 #define LEN 1024
 19 const int PORT = 8080;
 20 struct sockaddr_in client;
 21 struct sockaddr_in local;
 22 int listenSock;
 23 int linkSock = -1;
 24 int fds[64];
 25 int size_client = sizeof(client);
 26 
 27 int ListenSock()
 28 {
 29     listenSock = socket(AF_INET, SOCK_STREAM, 0);
 30     if(listenSock < 0)
 31     {
 32         perror("socket");
 33         exit(1);
 34     }
 35 
 36     local.sin_family = AF_INET;
 37     local.sin_addr.s_addr = htonl(INADDR_ANY);
 38     local.sin_port = htons(PORT);
 39 
 40     if ( bind(listenSock, (struct sockaddr*)&local, sizeof(local)) < 0)
 41     {
 42         perror("bind");
 43         exit(2);
 44     }
 45 
 46     if( listen(listenSock, 5) < 0)
 47     {
 48         perror("listen");
 49         exit(3);
 50     }
 51     return listenSock;
 52 }
 53 
 54 int main()
 55 {
 56      listenSock = ListenSock();       //进入监听状态
 57  
 58      char buf[LEN];
 59      memset(buf, '\0', LEN);
 60      while(1)
 61      {
 62          fd_set reads, writes;
 63          int fds_max;       //fds中最大的一个文件描述符
 64  
 65          int i = 0;
 66          int fds_num = sizeof(fds)/sizeof(fds[0]);
 67          for(; i < fds_num; i++)       //初始化fds
 68          {
 69              fds[i] = -1;
 70          }
 71  
 72          fds[0] = listenSock;
 73          fds_max = fds[0];
 74          struct timeval times;
 75  
 76          while(1)                                                                                                                         
 77          {
 78              FD_ZERO(&reads);                 //每次循环都要初始化,因为reads与writes即是是输入型参数,也是输出型参数
 79              FD_ZERO(&writes);
 80              FD_SET(listenSock, &reads);     //listenSock只关心读事件
 81              times.tv_sec = 10;
 82              times.tv_usec = 0;
 83              struct timeval times;
 84              for(i = 1; i < fds_num; i++ )   //在select之前把所有的文件描述符都设置读事件
 85              {
 86                  if(fds[i] > 0)
 87                  {
 88                      FD_SET(fds[i], &reads);  //所有的socket都要关心读事件
 89  
 90                      if(fds[i] > fds_max)
 91                      {
 92                          fds_max = fds[i];
 93                      }
 94                  }
 95              }
 96  
 97              switch( select(fds_max + 1, &reads, &writes, NULL, &times))     //select函数返回已就绪的文件描述符的个数
 98              {
 99                  case 0:
100                      printf("time out....!\n");
101                      break;
102                  case -1:
103                      perror("select");
104                      break;
105                  default:
106                      for(i = 0; i < fds_num; i++)
107                      {
108                          if(fds[i] == listenSock && FD_ISSET(fds[i], &reads))  //如果为listenSock并且已经就绪  则可以accept客户端了       
109                          {
110                              linkSock = accept(listenSock, (struct sockaddr*)&client, &size_client);
111                              if(linkSock < 0)
112                              {
113                                  perror("accept");
114                                  continue;
115                              }
116                                                                                                                                           
117                              printf("a new connect is create...... the fds is %d\n", linkSock);
118                              for(i = 0; i < fds_max; i++)   //把新创建的文件描述符放到fds中
119                              {
120                                  if(fds[i] < 0)
121                                  {
122                                      fds[i] = linkSock;                                                                                   
123                                      FD_SET(linkSock, &writes);     //设置进写事件队列中
124                                      break;
125                                  }
126                              }
127                              
128                              if(i == fds_max - 1)
129                              {
130                                  printf("文件描述符集已满,请关闭一些链接,以保证系统能正常工作!\n");
131                              }
132                          }
133                          else if(fds[i] > 0 && FD_ISSET(fds[i], &reads))     //服务器可以读取客户端发过来的信息了
134                          {
135                              memset(buf, '\0', LEN);
136                              int ret = read(fds[i], buf, LEN);
137                              if(ret < 0)   //读取错误,直接跳到下一个文件描述符
138                              {
139                                  perror("read");
140                                  continue;
141                              }
142                              else if(ret == 0)    //客户端关闭 直接跳到下一个文件描述符
143                              {
144                                  printf("client is closed!\n");
145                                  continue;
146                              }
147                              else    //读取成功
148                              {
149                                  buf[ret] = '\0';
150                                  printf("client# %s\n", buf);
151                              }
152 
153                              if( write(fds[i], buf, strlen(buf)) < 0)    //回显给客户端
154                              {
155                                  perror("write");
156                                  continue;
157                              }
158                          }
167                      }
168 
169                      break;
170              }
171          }
172      }
173 }


client.c:

1 /****************************************                                                                                                 
  2     > File Name:client.c
  3     > Author:xiaoxiaohui
  4     > mail:1924224891@qq.com
  5     > Created Time:2016年05月23日 星期一 12时30分01秒
  6 ****************************************/
  7 
  8 #include<stdio.h>
  9 #include<stdlib.h>
 10 #include<string.h>
 11 #include<sys/types.h>
 12 #include<sys/socket.h>
 13 #include<netinet/in.h>
 14 #include<arpa/inet.h>
 15 #include<sys/time.h>
 16 #include<unistd.h>
 17 
 18 #define LEN 1024
 19 const int PORT = 8080;
 20 const char* IP = "127.0.0.1";
 21 struct sockaddr_in server;
 22 int clientSock;
 23 char buf[LEN];
 24 
 25 int main()
 26 {
 27     clientSock = socket(AF_INET, SOCK_STREAM, 0);
 28     if(clientSock < 0)
 29     {
 30         perror("socket");
 31         exit(1);
 32     }
 33 
 34     server.sin_family = AF_INET;
 35     server.sin_addr.s_addr = inet_addr(IP);
 36     server.sin_port = htons(PORT);
 37 
 38     if ( connect(clientSock, (struct sockaddr*)&server, sizeof(server)) < 0)
 39     {
 40         perror("connect");
 41         exit(2);
 42     }
 43 
 44     while(1)
 45     {
 46         memset(buf, '\0', LEN);
 47         printf("please input: ");
 48         gets(buf);
 49         write(clientSock, buf, strlen(buf));
 50 
 51         memset(buf, '\0', LEN);
 52         int ret = read(clientSock, buf, LEN);
 53         buf[ret] = '\0';
 54         printf("echo: %s\n", buf);  
 55     }
 56 
 57     return 0;
 58 }


Makefile:

1 .PHONY:all                                                                                                                                
  2 all:server client
  3 
  4 server:server.c
  5     gcc -o $@ $^ -g
  6 client:client.c
  7     gcc -o $@ $^ -g
  8 
  9 .PHONY:clean
 10 clean:
 11     rm -f server client

执行结果:

linux下select函数详解及实例_函数详解_02


linux下select函数详解及实例_函数详解_03




五.总结:

select用于I/O复用,通过监听文件描述符的状态,当与文件描述符相关的资源准备就绪就返回,从而提高性能。

reads,writes, timeout都是输入/输出型参数,所以要在while循环内设置它们的状态。