在五种IO模型中我们认识了select,用于实现多路复用输入/输出模型。
回忆一下其作用:
1. select系统调用是用来让我们的程序监视多个文件描述符的状态变化的;
2. 程序会停在select这里等待,直到被监视的文件描述符有一个或多个发生了状态改变。
fd_set结构
注意到select函数中参数类型出现了fd_set结构,其实它是一个”位图”,使用位图中对应的位来表示要监视的文件描述符。
它的大小由系统决定,是有上限的,所以表示的文件描述符也是有上限的。
系统中提供了一组操作fd_set的接口, 来比较方便的操作位图:
函数 | 作用 |
---|---|
FD_CLR | 用来清除描述词组set中相关fd的位 |
FD_ISSET | 用来测试描述词组set中相关fd的位是否为真 |
FD_SET | 用来设置描述词组set中相关fd的位 |
FD_ZERO | 用来清除描述词组set的全部位 |
特定的数:执行成功则返回文件描述词状态已改变的个数
0:如果返回0代表在描述词状态改变前已超过timeout时间,没有返回
-1:当有错误发生时则返回-1,错误原因存于errno,此时参数readfds,writefds, exceptfds和timeout 的值变成不可预测。
上文提到过fd_set为位图,用来监视文件描述符集。要理解select模型,我们首先要理解fd_set。
比如:fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd.
(1)执行fdset set; FDZERO(&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被清空。且fd_set的大小由系统决定,是有上限的,所以表示的文件描述符也是有上限的
从select函数原型我们可知,select监视读、写、或者异常条件就绪。
那么在什么情况下,满足就绪条件呢?
socket上收到带外数据. 关于带外数据, 和TCP紧急模式相关。
比较与多线程和多进程,效率较高。
我们在代码里只是为了演示select的用法,假设数据可以一次从客户端取完,不用考虑数据粘包等问题,代码如下:
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include
9 #define MAX_FDS sizeof(fd_set)*8
10 #define INIT_DATA -1
11
12 static void initArray(int arr[],int num){
13 int i=0;
14 for(;i15 arr[i]=INIT_DATA;
16 }
17 }
18
19 static int addSockToArray(int arr[],int num,int fd){
20 int i=0;
21 for(;i22 if(arr[i]<0){
23 arr[i]=fd;
24 return i;
25 }
26 }
27 return -1; //full
28 }
29
30 int setArrayToFdSet(int arr[],int num,fd_set *rfds){
31 int i=0;
32 int max_fd=INIT_DATA;
33 for(;i34 if(arr[i]>=0){
35 FD_SET(arr[i],rfds);
36
37 if(max_fd38 max_fd=arr[i];
39 }
40 }
41 }
42 return max_fd;
43 }
44
45
46 static void serviceIO(int arr[],int num,fd_set *rfds){
47 int i=0;
48 for(;i49 if(arr[i]>INIT_DATA){
50 int fd=arr[i];
51 if(i==0&&FD_ISSET(arr[i],rfds)){
52 //listen ready
53 struct sockaddr_in client;
54 socklen_t len=sizeof(client);
55 int sock=accept(fd,(struct sockaddr*)&client,&len);
56 if(sock<0){
57 perror("accept");
58 continue;
59 }
60 printf("get a new client [%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port) );
61 //addArray
62 if(addSockToArray(arr,num,sock)==-1)
63 {
64 close(sock);
65 }
66
67 }
68 else if(i!=0&&FD_ISSET(arr[i],rfds)){
69 //normal fd ready
70 char buf[1024];
71 ssize_t s=read(fd,buf,sizeof(buf)-1);
72 if(s>0){
73 buf[s]=0;
74 printf("client:> %s\n",buf);
75 }
76 else if(s==0){
77 close(fd);
78 arr[i]=INIT_DATA;
79 printf("client quit!\n");
80 }
81 else{
82 perror("read\n");
83 close(fd);
84 arr[i]=INIT_DATA;
85 }
86 }
87 else{
88 //do nothing
89 }
90 }
91 }
92 }
93
94
95 int startup(int port){
96 int sock=socket(AF_INET,SOCK_STREAM,0);
97 if(sock<0){
98 perror("sock");
99 exit(2);
100 }
101
102 int opt=1;
103 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
104
105 struct sockaddr_in local;
106 local.sin_family=AF_INET;
107 local.sin_addr.s_addr=htonl(INADDR_ANY);
108 local.sin_port=htons(port);
109
110 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
111 perror("bind");
112 exit(3);
113 }
114 if(listen(sock,5)<0){
115 perror("listen");
116 exit(4);
117 }
118 return sock;
119 }
120 // ./select_server port
121 int main(int argc,char *argv[]){
122 if(argc!=2){
123 printf("Usage:%s [port]\n",argv[0]);
124 return 1;
125 }
126
127 int listen_sock=startup(atoi(argv[1]));
128
129 fd_set rfds;
130 int fd_array[MAX_FDS];
131 initArray(fd_array,MAX_FDS);
132 addSockToArray(fd_array,MAX_FDS,listen_sock);
133 for(;;){
134 FD_ZERO(&rfds);
135
136 int max_fd=setArrayToFdSet(fd_array,MAX_FDS,&rfds);
137
138 struct timeval timeout={3,0};
139 switch(select(max_fd+1,&rfds,NULL,NULL,NULL)){
140 case -1:
141 perror("select\n");
142 break;
143 case 0:
144 printf("timeout...\n");
145 break;
146 default:
147 serviceIO(fd_array,MAX_FDS,&rfds);
148 break;
149 }
150 }
151 }
测试:
用远程登录工具telnet测试,需要安装:
yum install telnet-server
yum install telnet
service xinetd restart
如下图: