什么是I/O多路转接?
对于多个非阻塞I/O,怎么知道I/O何时已经处于可读或可写状态?
如果采用循环一直调用write/read,直到返回成功,这样的方式称为轮询(polling)。大多数时间I/O没有处于就绪状态,因此这样的轮询十分浪费CPU。
而一种比较好的技术是使用I/O多路转接,也叫做I/O多路复用。其基本思想为:先构造一个有关描述符的列表,然后调用一个函数,直到这些描述符中的一个已经准备好进行I/O时,该函数才返回。在返回时,它告诉进程哪些描述符已经准备好可以进行I/O了。
接下来就是今天的重头戏select,曾有人认为select函数是I/O复用的全部内容。
select函数的功能
简单来说就是一个加强版的listen,select系统调用来让我们同时监视多个文件句柄的变化,然后程序会停在select这里等待,直到被监视的文件句柄有一个或者多个发生状态的变化。关于句柄就是文件描述符,其实也就是一个整数。
select函数的使用流程
我们下来就按这个流程使用select模拟实现服务器。
1.函数原型参数
nfds:是需要监视的最大文件描述符的值+1,也就是监视的文件描述符的数量
readfds:用户告诉内核,我关心哪些文件描述符上的读事件。
writefds:用户告诉内核,我关心哪些文件描述符上的写事件。
exceptfds : 用户告诉内核,我关心哪些文件描述符上的异常事件。
需要说明的是以上这三个参数都是作为输入型参数时的含义,作为输出型参数时表示我关心哪些已经就绪。
timeout : struct timeval 结构体类型。
timeout也就是设置等待时间为:
NULL:表示select没有timeout,select 将一直被阻塞,知道某个文件描述符发生变化。
0:仅检测描述符集合的状态,然后立即返回,并不等待外部事件的发生。
特定的时间值:如果在这个时间段内没有时间发生,select将超时返回。
2.返回值
成功:返回文件描述符状态已改变的个数。
为0:代表状态改变前已经超时,没有返回。
错误:返回-1,原因位于errno,参数readfds, writefds, exceptfds,timeout 则变成不可预测的随机值。
要理解select就要了解一个很重要的数据结构
fd_set
fd_set, select()机制中提供的一个数据结构,实际上是一个long类型的数组,每一个数组元素都能与一打开的文件句柄(不管是socket句柄,还是其他文件或命名管道或设备句柄)建立联系,建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,由此来通知执行了select()的进程哪一socket或文件发生了可读或可写事件。
这个结构数组中存的是0, 1 该位为1,则表示是文件描述符的监视对象。
我们也可以猜想,这个结构是用位图来实现的。这也正是select的优点,我们后边详细说明。
我们来看看具体怎么使用fd_set;
下面提供了宏来处理这几种描述词组
fd 为select的句柄
fd_set *set 表示指向结构数组的指针
FD_CLR() : 将set清零,使集合中不含任何fd
FD_ISSET() : 调用select,后用它检测fd是否在集合,存在则返回真,否则返回假(0).
FD_SET (): 将fd加入到集合中
FD_ZERO() : 将fd从集合中清除
总结下select函数的调用过程
先调用宏 FD_ZERO 将指定的 fd_set 清零,然后调用宏 FD_SET 将需要测试的 fd 添加进 fd_set,接着调用函数 select 测试 fd_set 中的所有fd,最后用宏 FD_ISSET 检查某个 fd 在函数select调用后,相应位是否仍然为1。
select模型的优缺点
优点:
(1)select相比较与多进程多线程的服务器来说可以一次等待多个文件描述符;
(2)当用户数量比较多是,占用的资源比较恒定,性能比较好;
缺点:
(1)一次所监视的文件描述符的数量有限,默认是1024;
(2)由于参数为输入/输出型参数,每次等待都需要对于文件句柄集进行遍历重置,当文件描述符的数目增多时,遍历的开销变大,性能会下降;
(3)用户数量增多时,select会频繁触发从内核态到用户态,从用户态到内核态对fd的拷贝,会导致性能的下降;
代码模拟:
1 #include <stdio.h>
2 #include <sys/time.h>
3 #include <sys/types.h>
4 #include <sys/select.h>
5 #include <sys/socket.h>
6 #include <arpa/inet.h>
7 #include <netinet/in.h>
8 #include <unistd.h>
9 #include <stdlib.h>
10
11 int rfds[128];
12
13 static void usage(const char* proc)
14 {
15 printf("Usage:%s[local_ip][local_port]\n", proc);
16 }
17
18 int startup(const char* ip, int port)
19 {
20 int sock = socket(AF_INET, SOCK_STREAM, 0);
21 if(sock < 0)
22 {
23 perror("socket");
24 return 1;
25 }
26 struct sockaddr_in local_server;
27 local_server.sin_family = AF_INET;
28 local_server.sin_port = htons(port);
29 local_server.sin_addr.s_addr = inet_addr(ip);
30
31 int opt = 1;
32 setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
33
34 if(bind(sock, (struct sockaddr*)&local_server, sizeof(local_server)) < 0)
35 {
36 perror("bind");
37 return 2;
38 }
39
40 if(listen(sock, 5) < 0)
41 {
42 perror("listen");
43 return 3;
44 }
45
46 return sock;
47 }
48
49 int main(int argc, char*argv[])
50 {
51 if(argc != 3)
52 {
53 usage(argv[0]);
54 return 1;
55 }
56
57 int listen_sock = startup(argv[1], atoi(argv[2]));
58
59 int i = 0;
60 for(; i < 128; i++)
61 {
62 rfds[i] = -1;
63 }
64
65 fd_set rset;
66 int max = 0;
67
68 while(1)
69 {
70 struct timeval timeout = {0, 0};
71 FD_ZERO(&rset);
72
73 rfds[0] = listen_sock;
74 max = listen_sock;
75
76 for(i = 0; i < 128; i++)
77 {
78 if(rfds[i] >= 0)
79 {
80 FD_SET(rfds[i], &rset);
81 if(max < rfds[i])
82 {
83 max = rfds[i];
84 }
85 }
86 }
87
88 switch(select(max + 1, &rset, NULL, NULL, NULL))
89 {
90 case -1:
91 perror("select");
92 break;
93 case 0:
94 printf("timeout...\n");
97 {
98 int j = 0;
99 for(; j < 128; j++)
100 {
101 if(rfds[j] < 0)
102 {
103 continue;
104 }
105 if((j == 0) && FD_ISSET(rfds[j], &rset))
106 {
107 struct sockaddr_in client;
108 socklen_t len = sizeof(client);
109 int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
110 if(new_sock < 0)
111 {
112 perror("accept");
113 }
114 else
115 {
117
118 int k = 0;
119 for(; k < 128; k++)
120 {
121 if(rfds[j] == -1)
122 {
123 rfds[k] = new_sock;
124 break;
125 }
126 }
127 if(k == 128)
128 {
129 close(new_sock);
130 }
131 }
132 }
133 else if(FD_ISSET(rfds[j], &rset))
134 {
135 char buf[1024];
136 ssize_t s = read(rfds[j], buf, sizeof(buf)-1);
137 if(s > 0)
138 {
139 buf[s] = 0;
140 printf("client# %s\n", buf);
141 }
142 else if(s == 0)
143 {
144 printf("the client is quit!\n");
145 close(rfds[j]);
146 rfds[j] = -1;
147 }
148 else
149 {
150 perror("read");
151 }
152 }
153 }
154 }
155 break;
156 }
157 }
158 return 0;
159 }