一.概述:

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



二.select函数:

以下为man文本中的解释:

 /* According to POSIX.1-2001 */       #include 
       /* According to earlier standards */       #include 
       #include 
       #include 
       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
  9 #include
 10 #include
 11 #include
 12 #include
 13 #include
 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 }

执行结果:

wKiom1dEKCKwKpafAAAo2P7Eie0227.png

(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
  9 #include
 10 #include
 11 #include
 12 #include
 13 #include
 14 #include
 15 #include
 16 #include
 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, ×))     //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
  9 #include
 10 #include
 11 #include
 12 #include
 13 #include
 14 #include
 15 #include
 16 #include
 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

执行结果:



五.总结:

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

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