IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数 可以避免同步非阻塞IO模型中轮询等待的问题,此外poll、epoll都是这种模型。在该种模式下,用户首先将需要进行IO操作的 socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起 read请求,读取数据并继续执行。从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加 监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理 多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处 理多个IO请求的目的。
select()函数允许进程指示内核等待多个事件(文件描述符)中的任何一个发生,并只在有一个或多个事件发生或经历一段指定时 间后才唤醒它,然后接下来判断究竟是哪个文件描述符发生了事件并进行相应的处理。
给出select()的参数
#include
#include
struct timeval
{
long tv_sec; //seconds
long tv_usec; //microseconds
};
FD_ZERO(fd_set* fds) //清空集合
FD_SET(int fd, fd_set* fds) //将给定的描述符加入集合
FD_ISSET(int fd, fd_set* fds) //判断指定描述符是否在集合中
FD_CLR(int fd, fd_set* fds) //将给定的描述符从文件中删除
int select(int max_fd, fd_set *readset, fd_set *writeset, fd_set *exceptset, struct timeval *timeout);
说明: select监视并等待多个文件描述符的属性发生变化,它监视的属性分3类,分别是readfds(文件描述符有数据到来可读)、 writefds(文件描述符可写)、和exceptfds(文件描述符异常)。调用后select函数会阻塞,直到有描述符就绪(有数据可读、可写、 或者有错误异常),或者超时( timeout 指定等待时间)发生函数才返回。当select()函数返回后,可以通过遍历 fdset,来找到 究竟是哪些文件描述符就绪。
下面是使用select()多路复用实现网络socket服务器多路并发的流程图:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/types.h>
4 #include<string.h>
5 #include<errno.h>
6 #include<unistd.h>
7 #include<ctype.h>
8 #include<getopt.h>
9 #include<time.h>
10 #include<pthread.h>
11 #include<libgen.h>
12 #include<arpa/inet.h>
13 #include<sys/socket.h>
14 #include<netinet/in.h>
15
16 #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
17
18 static inline void msleep(unsigned long ms);
19 static inline void print_usage(char *progname);
20 int socket_server_init(char *listen_ip,int listen_port);
21
22 int main(int argc, char **argv)
23 {
24 int listenfd;
25 int connfd;
26 int daemon_run = 0;
27 char *progname = NULL;
28 int opt;
29 fd_set rdset;
30 int rv;
31 int i,j;
32 int found;
33 int maxfd = 0;
34 char buf[1024];
35 int fds_array[1024];
36 int serv_port = 0;
37
38 struct option long_options[]=
39 {
40 {"daemon", no_argument, NULL, 'b'},
41 {"port", required_argument, NULL, 'p'},
42 {"help", no_argument, NULL, 'h'},
43 {NULL, 0, NULL, 0}
44 };
45
46 progname = basename(argv[0]);
47
48 /*Parser the command line parameters */
49 while((opt = getopt_long(argc,argv, "bh:p", long_options, NULL)) != -1)
50 {
51 switch(opt)
52 {
53 case 'b':
54 daemon_run = 1;
55 break;
56
57 case 'p':
58 serv_port = atoi(optarg);
59 break;
60
61 case 'h': /* Get help information */
62 print_usage(progname);
63 return EXIT_SUCCESS;
64
65 default:
66 break;
67 }
68 }
69
70 if(!serv_port)
71 {
72 print_usage(progname);
73 return -1;
74 }
75
76 if((listenfd = socket_server_init(NULL, serv_port)) < 0)
77 {
78 printf("ERROR:%s server listen on port %dfailture\n",argv[0],serv_port);
79 return -2;
80 }
81 printf("%s server start to listen on port %d\n", argv[0], serv_port);
82
83 /*set program running on background */
84 if(daemon_run)
85 {
86 daemon(0,0);
87 }
88
89 for(i = 0; i < ARRAY_SIZE(fds_array); i++)
90 {
91 fds_array[i] = -1;
92 }
93 fds_array[0] = listenfd;
94
95 for (; ;)
96 {
97 FD_ZERO(&rdset);
98 for(i = 0; i < ARRAY_SIZE(fds_array); i++)
99 {
100 if(fds_array[i] < 0)
101 continue;
102 maxfd = fds_array[i]>maxfd ? fds_array[i] : maxfd;
103 FD_SET(fds_array[i], &rdset);
104 }
105
106 /*program will blocked here*/
107 rv = select(maxfd+1, &rdset, NULL, NULL, NULL);
108 if(rv < 0)
109 {
110 printf("select failture: %s\n", strerror(errno));
111 break;
112 }
113 else if(rv==0)
114 {
115 printf("select get timeout\n");
116 continue;
117 }
118
119 /*listen socket get event means new client start connect now */
120 if( FD_ISSET(listenfd, &rdset))
121 {
122 if((connfd=accept(listenfd, (struct sockaddr *)NULL, NULL))<0)
123 {
124 printf("accept new clientfailture:%s\n",strerror(errno));
125 continue;
126 }
127
128 found = 0;
129 for(i = 0; i < ARRAY_SIZE(fds_array); i++)
130 {
131 if( fds_array[i] < 0)
132 {
133 printf("accept new client[%d]andadditintoar\n",
134 connfd);
135 fds_array[i] = connfd;
136 found = 1;
137 break;
138 }
139 }
140 if(!found)
141 {
142 printf("accept new client[%d] but full, so refuse it\n",
143 connfd);
144 close(connfd);
145 }
146 }
147
148 else /*data arrive from already connect client */
149 {
150 for(i=0; i < ARRAY_SIZE(fds_array); i++)
151 {
152 if(fds_array[i]<0 || !FD_ISSET(fds_array[i], &rdset))
153 continue;
154 if((rv=read(fds_array[i],buf,sizeof(buf))) <=0)
155 {
156 printf("socket[%d] read failtur disconnect.\n",
157 fds_array[i]);
158 close(fds_array[i]);
159 fds_array[1024] = -1;
160 }
161 else
162 {
163 printf("socket[%d] read get %d bytes data\n",
164 fds_array[i], rv);
165 for(j = 0; j < rv; j++)
166 buf[j]=toupper(buf[j]);
167
168 if(write(fds_array[i],buf,rv) < 0)
169 {
170 printf("socket[%d] write failture:%s\n",
171 fds_array[i],strerror(errno));
172 close(fds_array[i]);
173 fds_array[i] = -1;
174 }
175 }
176 }
177 }
178 }
179 Cleanup:
180 close(listenfd);
181 return 0;
182 }
183
184 static inline void msleep(unsigned long ms)
185 {
186 struct timeval tv;
187
188 tv.tv_sec = ms/1000;
189 tv.tv_usec = (ms%1000)*1000;
190
191 select(0, NULL, NULL, NULL, &tv);
192 }
193
194 static inline void print_usage(char *progname)
195 {
196 printf("Usage: %s [OPTION]...\n", progname);
197 printf("%s is a socket server progname, which used to verify client and echo back string fr om it\n",progname);
198 return;
199 }
200
201 int socket_server_init(char *listen_ip, int listen_port)
202 {
203 struct sockaddr_in servaddr;
204 int rv = 0;
205 int on = 1;
206 int listenfd;
207
208 if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
209 {
210 printf("Use socket() to create a TCP socket failture:%s\n",strerror(errno));
211 return -1;
212 }
213
214 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
215
216 memset(&servaddr, 0, sizeof(servaddr));
217 servaddr.sin_family = AF_INET;
218 servaddr.sin_port = htons(listen_port);
219
220 if(!listen_ip)
221 {
222 servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
223 }
224 else
225 {
226 if(inet_pton(AF_INET, listen_ip, &servaddr.sin_addr) <= 0)
227 {
228 printf("inet_pton() set listen IP address failture\n");
229 rv = -2;
230 goto Cleanup;
231 }
232 }
233
234 if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0)
235 {
236 printf("Use bind() to bind the TCP socket failture:%s\n",strerror(errno));
237 rv = -3;
238 goto Cleanup;
239 }
240
241 if(listen(listenfd, 13) < 0)
242 {
243 printf("Use bind() to bind the TCP socket failture:%s\n",strerror(errno));
244 rv = -4;
245 goto Cleanup;
246 }
247
248 Cleanup:
249 if(rv<0)
250 close(listenfd);
251 else
252 rv = listenfd;
253 return rv;
254 }