1. 关于select的基础知识

    a. select是系统提供的多路复用输入输出模型。它是用来监视多个文件句柄的状态变化。

    b. 程序会停在select等,直到被监视的文件句柄至少有一个发生了状态改变。

    c. 文件句柄是整数。例如:我们最熟悉的句柄是0、1、2,0是标准输入,1是标准输出,2是标准错误输出。0、1、2对应的FILE *结构的表示是stdin、stdout、stderr。


2. 函数

(1)select()

I/O多路转接之select——基于TCP协议_I/O

a. 参数

nfds:需要监视的最大文件描述符值加1;

readfds:select监视的可读文件句柄集合。

writefds: select监视的可写文件句柄集合。

exceptfds:select监视的异常文件句柄集合。

timeout:是struct timeval结构用于描述一段时间长度。用来设置select()的等待时间,其结构定义如下:

I/O多路转接之select——基于TCP协议_select_02

如果参数timeout设为:

  1. NULL:select将一直被阻塞,直到某个文件描述符上发生了事件。

  2. 0:select将以非阻塞方式等,即检测描述符集的状态后立即返回,并不等待外部事件的发生。

  3. 大于0的值:

  • 如果在指定的时间段里没有事件发生,select将超时返回0。

  • 当readfds或writefds中映象的文件可读或可写或超时,本次select()结束返回。程序员利用一组系统提供的宏在select()结束时便可判断哪一文件可读或可写。

(见/usr/sys/select.h,可精确至百万分之一秒!)

★除第一个外,所有参数既是输入型参数又是输出型参数。


b.返回值

  • 成功则返回文件描述词状态已改变的数目;

  • 有错误返回-1;

  • 超过timeout时间,返回0。


(2)几行相关的宏解释如下:

void FD_CLR(int fd, fd_set *set);  //清除文件句柄fd与set的联系。
int  FD_ISSET(int fd, fd_set *set); //检查set联系的文件句柄fd是否可读写,当>0表示可读写。
void FD_SET(int fd, fd_set *set);  //建立文件句柄fd与set的联系。
void FD_ZERO(fd_set *set);       //清空set与所有文件句柄的联系。

(关于fd_set及相关宏的定义见/usr/include/sys/types.h


3.代码实现

 //select_server.c
 1 #include<stdio.h>
 2 #include<stdlib.h>
 3 #include<assert.h>
 4 #include<sys/select.h>
 5 #include<sys/types.h>
 6 #include<sys/socket.h>
 7 #include<arpa/inet.h>
 8 #include<netinet/in.h>
 9 
 10 #define _BACKLOG_ 5
 11 //将待处理的文件句柄保存放到select监控集中的fd
 12 int fds[64];
 13 
 14 static void usage(const char* proc)
 15 {
 16     printf("usage:%s [ip] [port]\n",proc);
 17 }
 18 static int startup(char* ip,int port)
 19 {
 20     assert(ip);
 21     //创建socket
 22     int sock=socket(AF_INET,SOCK_STREAM,0);
 23     if(sock<0)
 24     {
 25         perror("socket");
 26         exit(1);
 27     }
 28     
 28     //设置本地server端网络地址信息
 29     struct sockaddr_in local;
 30     local.sin_family=AF_INET;
 31     local.sin_port=htons(port);
 32     local.sin_addr.s_addr=inet_addr(ip);
 33     //绑定
 34     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 35     {
 36         perror("bind");
 37         exit(2);
 38     }
 39     //监听
 40     if(listen(sock,_BACKLOG_)<0)
 41     {
 42         perror("listen");
 43         exit(3);
 44     }
 45     return sock;
 46 }
 47 int main(int argc,char*argv[])
 48 {
 49     if(argc!=3)
 50     {
 51         usage(argv[0]);
 52         exit(1);
 53     }
 54 
 55     char* ip=argv[1];
 56     int port=atoi(argv[2]);
 57     //获取socket
 58     int listen_sock=startup(ip,port);
 59 
 60     int done=0;
 61     int new_sock=-1;
 62     struct sockaddr_in client;//创建网络地址信息结构体用于保存对端信息
 63     socklen_t len=sizeof(client);
 64 
 65     int max_fd;//最大文件描述符
 66     fd_set _reads;//可读文件句柄集合
 67     fd_set _writes;//可写文件句柄集合
 68 
 69     int i=0;
 70     int fds_num=sizeof(fds)/sizeof(fds[0]);
 71     for(;i<fds_num;++i)//初始化发fds
 72     {
 73         fds[i]=-1;
 74     }
 75     fds[0]=listen_sock;//将fds的第一个元素设为监听套接字
 76     max_fd=fds[0];//最大文件描述符设为fds第一个元素
 77 
 78     while(!done)
 79     {
 80         FD_ZERO(&_reads);//清空_reads与所有文件句柄的联系。
 81         FD_ZERO(&_writes);//清空_writes与所有文件句柄的联系。
 82         FD_SET(listen_sock,&_reads);建立文件句柄lisen_sock与_reads的联系
 83         struct timeval _timeout={5,0};//设定超时时间
 84         for(i=1;i<fds_num;++i)//循环fds(加fd,取maxfd)
 85         {
 86             if(fds[i]>0)
 87             {
 88                 FD_SET(fds[i],&_reads);
 89                 if(fds[i]>max_fd)
 90                 {
 91                     max_fd=fds[i];
 92                 }
 93             }
 94         }
 95         switch(select(max_fd+1,&_reads,&_writes,NULL,NULL))
 96         {
 97             case 0:
 98                 //超时
 99                 printf("timeout\n");
100                 break;
101             case -1://出错
102                 perror("select");
103                 break;
104             default://至少有一个IO事件已经就绪
105                 {
106                     i=0;
107                     for(;i<fds_num;++i)
108                     {
109                         if(fds[i]==listen_sock&&FD_ISSET(fds[i],&_reads))
110                         {
111                             //listen socket is ready!
112                             new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
113                             if(new_sock<0)
114                             {
115                                 perror("accept");
116                                 continue;
117                             }
118                             printf("get a new connect...%d\n",new_sock);
119                             for(i=0;i<fds_num;++i)//将新的连接请求加入到fds
120                             {
121                                 if(fds[i]==-1)
122                                 {
123                                     fds[i]=new_sock;
124                                     break;
125                                 }
126                             }
127                             if(i==fds_num)//如果i等于fds数组的大小,关掉new_sock
128                             { 
129                                 close(new_sock);
130                             }
131                         }
132                         //别的文件句柄
133                         else if(fds[i]>0 && FD_ISSET(fds[i],&_reads))
134                         {
135                             char buf[1024];
136                             ssize_t _s=read(fds[i],buf,sizeof(buf)-1);
137                             if(_s>0)
138                             {
139                                 //read sucess
140                                 buf[_s]='\0';
141                                 printf("client:%s\n",buf);
142                             }
143                             else if(_s==0)
144                             {
145                                 //client shutdown
146                                 printf("client shutdown...\n");
147                                 close(fds[i]);
148                                 fds[i]=-1;
149                             }
150                             else
151                             {
152                                 perror("read");
153                             }
154                         }
155                         //normal socket
156                         else
157                         {}
158                     }
159                 }
160                 break;
161         }
162     }
163     return 0;
164 }

//select_client.c
  1 #include<stdio.h>
  2 #include<string.h>
  3 #include<sys/select.h>
  4 int main()
  5 {
  6     int std_in=0;
  7 
  8     int max_fd=std_in;
  9     struct timeval _timeout={5,0};
 10 
 11     fd_set _reads;
 12 
 13     FD_ZERO(&_reads);
 14     FD_SET(std_in,&_reads);
 15 
 16     int done=0;
 17     while(!done)
 18     {
 19         //set timeout
 20         _timeout.tv_sec=5;
 21         _timeout.tv_usec=0;
 22 
 23         switch(select(max_fd+1,&_reads,NULL,NULL,&_timeout))
 24         {
 25             case -1:
 26                 //error
 27                 perror("select");
 28                 break;
 29             case 0:
 30                 printf("select timeout...\n");
 31                 break;
 32             default:
 33                 {
 34                     if(FD_ISSET(std_in,&_reads))
 35                     {
 36                         //read event ready
 37                         char buf[1024];
 38                         memset(buf,'\0',sizeof(buf));
 39                         ssize_t _s = read(std_in,buf,sizeof(buf)-1);
 40                         if(_s>0)
 41                         {
 42                             buf[_s]='\0';
 43                             if(strncmp(buf,"quit",4)==0)
 44                             {
 45                                 done=1;
 46                                 continue;
 47                             }
 48                             printf("echo:%s\n",buf);
 49                         }
 50                     }
 51                 }
 52                 break;
 53         }
 54     }
 55     return 0;
 56 }
 
 //makefile
  1 .PHONY:all
  2 all:select_server select_client
  3 select_server:select_server.c
  4     gcc -o $@ $^
  5 select_client:select_client.c
  6     gcc -o $@ $^
  7 .PHONY:clean
  8 clean:
  9     rm -f select_server select_client
  
  //start.sh
  1 #!/bin/bash
  2 
  3 service iptables stop
  4 ./select_server 192.168.163.128 8080

输出结果:

修正版:

  //select_client.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<string.h>
  4 #include<sys/select.h>
  5 #include<sys/socket.h>
  6 #include<netinet/in.h>
  7 #include<unistd.h>
  8 
  9 void usage(const char* proc)
 10 {   
 11     printf("%s [ip] [port]\n",proc);
 12     exit(1);
 13 }
 14 int main(int argc,char* argv[])
 15 {
 16     if(argc!=3)
 17     {
 18         usage(argv[0]);
 19         exit(1);
 20     }
 21     int server_ip=inet_addr(argv[1]);
 22     int server_port=atoi(argv[2]);
 23 
 24     int client_sock=socket(AF_INET,SOCK_STREAM,0);
 25     if(client_sock<0)
 26     {
 27         perror("socket");
 28         exit(2);
 29     }
 30     struct sockaddr_in server;
 31     server.sin_family=AF_INET;
 32     server.sin_addr.s_addr=server_ip;
 33     server.sin_port=htons(server_port);
 34 
 35     if(connect(client_sock,(struct sockaddr*)&server,sizeof(server))<0)
 36     {
 37         perror("connect");
 38         exit(3);
 39     }
 40     char buf[1024];
 41     while(1)
 42     {
 43         memset(buf,'\0',sizeof(buf));
 44         printf("Please Input: ");
 45         fflush(stdout);
 46         fgets(buf,sizeof(buf)-1,stdin);
 47         if(send(client_sock,buf,sizeof(buf)-1,0)<0)
 48         {
 49             perror("send");
 50             continue;
 51         }
 52         ssize_t _size=recv(client_sock,buf,sizeof(buf)-1,0);
 53         if(_size>0)
 54         {
 55             buf[_size]='\0';
 56             printf("server receive:%s\n",buf);
 57         }
 58     }
 59     return 0;
 60 }
 
 //select_server.c
  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<assert.h>
  4 #include<sys/select.h>
  5 #include<sys/types.h>
  6 #include<sys/socket.h>
  7 #include<arpa/inet.h>
  8 #include<netinet/in.h>
  9 
 10 #define _BACKLOG_ 5
 11 
 12 int fds[64];
 13 
 14 static void usage(const char* proc)
 15 {   
 16     printf("usage:%s [ip] [port]\n",proc);
 17 }
 18 static int startup(char* ip,int port)
 19 {
 20     assert(ip);
 21     //
 22     int sock=socket(AF_INET,SOCK_STREAM,0);
 23     if(sock<0)
 24     {
 25         perror("socket");
 26         exit(1);
 27     }
 28     //
 29     struct sockaddr_in local;
 30     local.sin_family=AF_INET;
 31     local.sin_port=htons(port);
 32     local.sin_addr.s_addr=inet_addr(ip);
 33     //
 34     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 35     {
 36         perror("bind");
 37         exit(2);
 38     }
 39     //
 40     if(listen(sock,_BACKLOG_)<0)
 41     {
 42         perror("listen");
 43         exit(3);
 44     }
 45     return sock;
 46 }
 47 int main(int argc,char*argv[])
 48 {
 49     if(argc!=3)
 50     {
 51         usage(argv[0]);
 52         exit(1);
 53     }
 54 
 55     char* ip=argv[1];
 56     int port=atoi(argv[2]);
 57 
 58     int listen_sock=startup(ip,port);
 59 
 60     int done=0;
 61     int new_sock=-1;
 62     struct sockaddr_in client;
 63     socklen_t len=sizeof(client);
 64 
 65     int max_fd;
 66     fd_set _reads;
 67     fd_set _writes;
 68 
 69     int i=0;
 70     int fds_num=sizeof(fds)/sizeof(fds[0]);
 71     for(;i<fds_num;++i)
 72     {
 73         fds[i]=-1;
 74     }
 75     fds[0]=listen_sock;
 76     max_fd=fds[0];
 77 
 78     while(!done)
 79     {
 80         FD_ZERO(&_reads);
 81         FD_ZERO(&_writes);
 82         FD_SET(listen_sock,&_reads);
 83         struct timeval _timeout={5,0};
 84         for(i=1;i<fds_num;++i)
 85         {
 86             if(fds[i]>0)
 87             {
 88                 FD_SET(fds[i],&_reads);
 89                 FD_SET(fds[i],&_writes);
 90                 if(fds[i]>max_fd)
 91                 {
 92                     max_fd=fds[i];
 93                 }
 94             }
 95         }
 96         switch(select(max_fd+1,&_reads,&_writes,NULL,&_timeout))
 97         {
 98             case 0:
 99                 //timeout
100                 printf("timeout...\n");
101                 break;
102             case -1:
103                 perror("select");
104                 break;
105             default:
106                 {
107                     i=0;
108                     for(;i<fds_num;++i)
109                     {
110                         if(fds[i]==listen_sock&&FD_ISSET(fds[i],&_reads))
111                         {
112                             //listen socket is ready!
113                             new_sock=accept(listen_sock,(struct sockaddr*)&client,&len);
114                             if(new_sock<0)
115                             {
116                                 perror("accept");
117                                 continue;
118                             }
119                             char* client_ip=inet_ntoa(client.sin_addr);
120                             int client_port=ntohs(client.sin_port);
121                             printf("get a new connect...[ip]:%s  [port]:%d\n",client_ip,client_port);
122                             for(i=0;i<fds_num;++i)
123                             {
124                                 if(fds[i]==-1)
125                                 {
126                                     fds[i]=new_sock;
127                                     break;
128                                 }
129                             }
130                             if(i==fds_num)
131                             {
132                                 close(new_sock);
133                             }
134                         }
135                         //listen socket
136                         else if(fds[i]>0 && FD_ISSET(fds[i],&_reads))
137                         {
138                             char buf[1024];
139                             ssize_t _s=read(fds[i],buf,sizeof(buf)-1);
140                             if(_s>0)
141                             {
142                                 //read sucess
143                                 buf[_s]='\0';
144                                 printf("client:%s",buf);
145                                 if(FD_ISSET(fds[i],&_writes));
146                                 {
147                                     _s=write(fds[i],buf,sizeof(buf)-1);
148                                     if(_s>0)
149                                     {
150                                         //write sucess
151                                      //   buf[_s]='\0';
152                                     //    printf("server:%s\n",buf);
153                                     }
154                                     else if(_s<0)
155                                     {
156                                         perror("write");
157                                         exit(1);
158                                     }
159                                     else
160                                     {
161                                         printf("can not write back...\n");
162                                     }
163                                 }
164                             }
165                             else if(_s==0)
166                             {
167                                 //client shutdown
168                                 printf("client shutdown...\n");
169                                 close(fds[i]);
170                                 fds[i]=-1;
171                             }
172                             else
173                             {
174                                 perror("read");
175                             }
176                         }
177                         //normal socket
178                         else
179                         {}
180                     }
181                 }
182                 break;
183         }
184     }
185     return 0;
186 }

输出结果:

I/O多路转接之select——基于TCP协议_TCP_03