南昌冶金建设有限公司网站,著名logo设计,哪里有免费网站可以看,win7 搭建iss网站select#xff0c;poll#xff0c;epoll都是IO多路复用的机制。I/O多路复用就通过一种机制#xff0c;可以监视多个描述符#xff0c;一旦某个描述符就绪#xff08;一般是读就绪或者写就绪#xff09;#xff0c;能够通知程序进行相应的读写操作。但select#xff0c;…selectpollepoll都是IO多路复用的机制。I/O多路复用就通过一种机制可以监视多个描述符一旦某个描述符就绪一般是读就绪或者写就绪能够通知程序进行相应的读写操作。但selectpollepoll本质上都是同步I/O因为他们都需要在读写事件就绪后自己负责进行读写也就是说这个读写过程是阻塞的而异步I/O则无需自己负责进行读写异步I/O的实现会负责把数据从内核拷贝到用户空间。
一、select实现
1.1.基本概念
IO多路复用是指内核一旦发现进程指定的一个或者多个IO条件准备读取它就通知该进程。
IO多路复用适用如下场合
当客户处理多个描述字时一般是交互式输入和网络套接口必须使用I/O复用。当一个客户同时处理多个接口时而这种情况是可能的但很少出现。如果一个TCP服务器既要处理监听套接口又要处理已连接套接口一般也要用到I/O复用。如果一个服务器即要处理TCP又要处理UDP一般要使用I/O复用。如果一个服务器要处理多个服务或多个协议一般要使用I/O复用。
与多进程和多线程技术相比I/O多路复用技术的最大优势是系统开销小系统不必创建进程/线程也不必维护这些进程/线程从而大大减小了系统的开销。
1.2.select函数
该函数准许进程指示内核等待多个事件中的任何一个发送并只在有一个或多个事件发生或经历一段指定的时间后才唤醒。函数原型如下
#include sys/select.h
#include sys/time.hint select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout)
返回值就绪描述符的数目超时返回0出错返回-1
函数参数介绍如下
1第一个参数maxfdp1指定待测试的描述字个数它的值是待测试的最大描述字加1因此把该参数命名为maxfdp1描述字0、1、2...maxfdp1-1均将被测试。
因为文件描述符是从0开始的。
2中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件的描述字。如果对某一个条件不感兴趣就可以把它设为空指针。struct fd_set可以理解为一个集合这个集合中存放的是文件描述符可通过以下四个宏进行设置
void FD_ZERO(fd_set *fdset); //清空集合void FD_SET(int fd, fd_set *fdset); //将一个给定的文件描述符加入集合之中void FD_CLR(int fd, fd_set *fdset); //将一个给定的文件描述符从集合中删除int FD_ISSET(int fd, fd_set *fdset); // 检查集合中指定的文件描述符是否可以读写
3timeout告知内核等待所指定描述字中的任何一个就绪可花多少时间。其timeval结构用于指定这段时间的秒数和微秒数。
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
这个参数有三种可能
1永远等待下去仅在有一个描述字准备好I/O时才返回。为此把该参数设置为空指针NULL。
2等待一段固定时间在有一个描述字准备好I/O时返回但是不超过由该参数所指向的timeval结构中指定的秒数和微秒数。
3根本不等待检查描述字后立即返回这称为轮询。为此该参数必须指向一个timeval结构而且其中的定时器值必须为0。
1.3.测试程序
写一个TCP回射程序程序的功能是客户端向服务器发送信息服务器接收并原样发送给客户端客户端显示出接收到的信息。
服务端程序如下
1 #include stdio.h2 #include stdlib.h3 #include string.h4 #include errno.h5 #include netinet/in.h6 #include sys/socket.h7 #include sys/select.h8 #include sys/types.h9 #include netinet/in.h10 #include arpa/inet.h11 #include unistd.h12 #include assert.h13 14 #define IPADDR 127.0.0.115 #define PORT 878716 #define MAXLINE 102417 #define LISTENQ 518 #define SIZE 1019 20 typedef struct server_context_st21 {22 int cli_cnt; /*客户端个数*/23 int clifds[SIZE]; /*客户端的个数*/24 fd_set allfds; /*句柄集合*/25 int maxfd; /*句柄最大值*/26 } server_context_st;27 static server_context_st *s_srv_ctx NULL;28 /*29 * */30 static int create_server_proc(const char* ip,int port)31 {32 int fd;33 struct sockaddr_in servaddr;34 fd socket(AF_INET, SOCK_STREAM,0);35 if (fd -1) {36 fprintf(stderr, create socket fail,erron:%d,reason:%s\n,37 errno, strerror(errno));38 return -1;39 }40 41 /*一个端口释放后会等待两分钟之后才能再被使用SO_REUSEADDR是让端口释放后立即就可以被再次使用。*/42 int reuse 1;43 if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, reuse, sizeof(reuse)) -1) {44 return -1;45 }46 47 bzero(servaddr,sizeof(servaddr));48 servaddr.sin_family AF_INET;49 inet_pton(AF_INET,ip,servaddr.sin_addr);50 servaddr.sin_port htons(port);51 52 if (bind(fd,(struct sockaddr*)servaddr,sizeof(servaddr)) -1) {53 perror(bind error: );54 return -1;55 }56 57 listen(fd,LISTENQ);58 59 return fd;60 }61 62 static int accept_client_proc(int srvfd)63 {64 struct sockaddr_in cliaddr;65 socklen_t cliaddrlen;66 cliaddrlen sizeof(cliaddr);67 int clifd -1;68 69 printf(accpet clint proc is called.\n);70 71 ACCEPT:72 clifd accept(srvfd,(struct sockaddr*)cliaddr,cliaddrlen);73 74 if (clifd -1) {75 if (errno EINTR) {76 goto ACCEPT;77 } else {78 fprintf(stderr, accept fail,error:%s\n, strerror(errno));79 return -1;80 }81 }82 83 fprintf(stdout, accept a new client: %s:%d\n,84 inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);85 86 //将新的连接描述符添加到数组中87 int i 0;88 for (i 0; i SIZE; i) {89 if (s_srv_ctx-clifds[i] 0) {90 s_srv_ctx-clifds[i] clifd;91 s_srv_ctx-cli_cnt;92 break;93 }94 }95 96 if (i SIZE) {97 fprintf(stderr,too many clients.\n);98 return -1;99 }
100
101 }
102
103 static int handle_client_msg(int fd, char *buf)
104 {
105 assert(buf);
106 printf(recv buf is :%s\n, buf);
107 write(fd, buf, strlen(buf) 1);
108 return 0;
109 }
110
111 static void recv_client_msg(fd_set *readfds)
112 {
113 int i 0, n 0;
114 int clifd;
115 char buf[MAXLINE] {0};
116 for (i 0;i s_srv_ctx-cli_cnt;i) {
117 clifd s_srv_ctx-clifds[i];
118 if (clifd 0) {
119 continue;
120 }
121 /*判断客户端套接字是否有数据*/
122 if (FD_ISSET(clifd, readfds)) {
123 //接收客户端发送的信息
124 n read(clifd, buf, MAXLINE);
125 if (n 0) {
126 /*n0表示读取完成客户都关闭套接字*/
127 FD_CLR(clifd, s_srv_ctx-allfds);
128 close(clifd);
129 s_srv_ctx-clifds[i] -1;
130 continue;
131 }
132 handle_client_msg(clifd, buf);
133 }
134 }
135 }
136 static void handle_client_proc(int srvfd)
137 {
138 int clifd -1;
139 int retval 0;
140 fd_set *readfds s_srv_ctx-allfds;
141 struct timeval tv;
142 int i 0;
143
144 while (1) {
145 /*每次调用select前都要重新设置文件描述符和时间因为事件发生后文件描述符和时间都被内核修改啦*/
146 FD_ZERO(readfds);
147 /*添加监听套接字*/
148 FD_SET(srvfd, readfds);
149 s_srv_ctx-maxfd srvfd;
150
151 tv.tv_sec 30;
152 tv.tv_usec 0;
153 /*添加客户端套接字*/
154 for (i 0; i s_srv_ctx-cli_cnt; i) {
155 clifd s_srv_ctx-clifds[i];
156 /*去除无效的客户端句柄*/
157 if (clifd ! -1) {
158 FD_SET(clifd, readfds);
159 }
160 s_srv_ctx-maxfd (clifd s_srv_ctx-maxfd ? clifd : s_srv_ctx-maxfd);
161 }
162
163 /*开始轮询接收处理服务端和客户端套接字*/
164 retval select(s_srv_ctx-maxfd 1, readfds, NULL, NULL, tv);
165 if (retval -1) {
166 fprintf(stderr, select error:%s.\n, strerror(errno));
167 return;
168 }
169 if (retval 0) {
170 fprintf(stdout, select is timeout.\n);
171 continue;
172 }
173 if (FD_ISSET(srvfd, readfds)) {
174 /*监听客户端请求*/
175 accept_client_proc(srvfd);
176 } else {
177 /*接受处理客户端消息*/
178 recv_client_msg(readfds);
179 }
180 }
181 }
182
183 static void server_uninit()
184 {
185 if (s_srv_ctx) {
186 free(s_srv_ctx);
187 s_srv_ctx NULL;
188 }
189 }
190
191 static int server_init()
192 {
193 s_srv_ctx (server_context_st *)malloc(sizeof(server_context_st));
194 if (s_srv_ctx NULL) {
195 return -1;
196 }
197
198 memset(s_srv_ctx, 0, sizeof(server_context_st));
199
200 int i 0;
201 for (;i SIZE; i) {
202 s_srv_ctx-clifds[i] -1;
203 }
204
205 return 0;
206 }
207
208 int main(int argc,char *argv[])
209 {
210 int srvfd;
211 /*初始化服务端context*/
212 if (server_init() 0) {
213 return -1;
214 }
215 /*创建服务,开始监听客户端请求*/
216 srvfd create_server_proc(IPADDR, PORT);
217 if (srvfd 0) {
218 fprintf(stderr, socket create or bind fail.\n);
219 goto err;
220 }
221 /*开始接收并处理客户端请求*/
222 handle_client_proc(srvfd);
223 server_uninit();
224 return 0;
225 err:
226 server_uninit();
227 return -1;
228 }
客户端程序如下
1 #include netinet/in.h2 #include sys/socket.h3 #include stdio.h4 #include string.h5 #include stdlib.h6 #include sys/select.h7 #include time.h8 #include unistd.h9 #include sys/types.h
10 #include errno.h
11
12 #define MAXLINE 1024
13 #define IPADDRESS 127.0.0.1
14 #define SERV_PORT 8787
15
16 #define max(a,b) (a b) ? a : b
17
18 static void handle_recv_msg(int sockfd, char *buf)
19 {
20 printf(client recv msg is:%s\n, buf);
21 sleep(5);
22 write(sockfd, buf, strlen(buf) 1);
23 }
24
25 static void handle_connection(int sockfd)
26 {
27 char sendline[MAXLINE],recvline[MAXLINE];
28 int maxfdp,stdineof;
29 fd_set readfds;
30 int n;
31 struct timeval tv;
32 int retval 0;
33
34 while (1) {
35
36 FD_ZERO(readfds);
37 FD_SET(sockfd,readfds);
38 maxfdp sockfd;
39
40 tv.tv_sec 5;
41 tv.tv_usec 0;
42
43 retval select(maxfdp1,readfds,NULL,NULL,tv);
44
45 if (retval -1) {
46 return ;
47 }
48
49 if (retval 0) {
50 printf(client timeout.\n);
51 continue;
52 }
53
54 if (FD_ISSET(sockfd, readfds)) {
55 n read(sockfd,recvline,MAXLINE);
56 if (n 0) {
57 fprintf(stderr,client: server is closed.\n);
58 close(sockfd);
59 FD_CLR(sockfd,readfds);
60 return;
61 }
62
63 handle_recv_msg(sockfd, recvline);
64 }
65 }
66 }
67
68 int main(int argc,char *argv[])
69 {
70 int sockfd;
71 struct sockaddr_in servaddr;
72
73 sockfd socket(AF_INET,SOCK_STREAM,0);
74
75 bzero(servaddr,sizeof(servaddr));
76 servaddr.sin_family AF_INET;
77 servaddr.sin_port htons(SERV_PORT);
78 inet_pton(AF_INET,IPADDRESS,servaddr.sin_addr);
79
80 int retval 0;
81 retval connect(sockfd,(struct sockaddr*)servaddr,sizeof(servaddr));
82 if (retval 0) {
83 fprintf(stderr, connect fail,error:%s\n, strerror(errno));
84 return -1;
85 }
86
87 printf(client send to server .\n);
88 write(sockfd, hello server, 32);
89
90 handle_connection(sockfd);
91
92 return 0;
93 }
4、程序结果
启动服务程序执行三个客户程序进行测试结果如下图所示 select的调用过程如下所示
1使用copy_from_user从用户空间拷贝fd_set到内核空间2注册回调函数__pollwait3遍历所有fd调用其对应的poll方法对于socket这个poll方法是sock_pollsock_poll根据情况会调用到tcp_poll,udp_poll或者datagram_poll4以tcp_poll为例其核心实现就是__pollwait也就是上面注册的回调函数。5__pollwait的主要工作就是把current当前进程挂到设备的等待队列中不同的设备有不同的等待队列对于tcp_poll来说其等待队列是sk-sk_sleep注意把进程挂到等待队列中并不代表进程已经睡眠了。在设备收到一条消息网络设备或填写完文件数据磁盘设备后会唤醒设备等待队列上睡眠的进程这时current便被唤醒了。6poll方法返回时会返回一个描述读写操作是否就绪的mask掩码根据这个mask掩码给fd_set赋值。7如果遍历完所有的fd还没有返回一个可读写的mask掩码则会调用schedule_timeout是调用select的进程也就是current进入睡眠。当设备驱动发生自身资源可读写后会唤醒其等待队列上睡眠的进程。如果超过一定的超时时间schedule_timeout指定还是没人唤醒则调用select的进程会重新被唤醒获得CPU进而重新遍历fd判断有没有就绪的fd。8把fd_set从内核空间拷贝到用户空间。
总结
select的几大缺点
每次调用select都需要把fd集合从用户态拷贝到内核态这个开销在fd很多时会很大同时每次调用select都需要在内核遍历传递进来的所有fd这个开销在fd很多时也很大select支持的文件描述符数量太小了默认是1024
学习直通车
Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂ke.qq.com/course/4032547?flowToken1040236ke.qq.com/course/4032547?flowToken1040236ke.qq.com/course/4032547?flowToken1040236
内核资料直通车
Linux内核源码技术学习路线视频教程内核源码docs.qq.com/doc/DTXFKZlZ4YmFYQWZS正在上传…重新上传取消
二poll实现
poll的实现和select非常相似只是描述fd集合的方式不同poll使用pollfd结构而不是select的fd_set结构其他的都差不多。
1、基本知识 poll的机制与select类似与select在本质上没有多大差别管理多个描述符也是进行轮询根据描述符的状态进行处理但是poll没有最大文件描述符数量的限制。poll和select同样存在一个缺点就是包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间而不论这些文件描述符是否就绪它的开销随着文件描述符数量的增加而线性增大。
2.2。poll函数
函数格式如下所示
# include poll.h
int poll ( struct pollfd * fds, unsigned int nfds, int timeout);
pollfd结构体定义如下
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 等待的事件 */
short revents; /* 实际发生了的事件 */
} ;
每一个pollfd结构体指定了一个被监视的文件描述符可以传递多个结构体指示poll()监视多个文件描述符。每个结构体的events域是监视该文件描述符的事件掩码由用户来设置这个域。revents域是文件描述符的操作结果事件掩码内核在调用返回时设置这个域。events域中请求的任何事件都可能在revents域中返回。合法的事件如下
POLLIN 有数据可读。POLLRDNORM 有普通数据可读。POLLRDBAND 有优先数据可读。POLLPRI 有紧迫数据可读。POLLOUT 写数据不会导致阻塞。POLLWRNORM 写普通数据不会导致阻塞。POLLWRBAND 写优先数据不会导致阻塞。POLLMSGSIGPOLL 消息可用。此外revents域中还可能返回下列事件 POLLER 指定的文件描述符发生错误。POLLHUP 指定的文件描述符挂起事件。POLLNVAL 指定的文件描述符非法。
这些事件在events域中无意义因为它们在合适的时候总是会从revents中返回。
使用poll()和select()不一样你不需要显式地请求异常情况报告。 POLLIN | POLLPRI等价于select()的读事件POLLOUT |POLLWRBAND等价于select()的写事件。POLLIN等价于POLLRDNORM |POLLRDBAND而POLLOUT则等价于POLLWRNORM。例如要同时监视一个文件描述符是否可读和可写我们可以设置 events为POLLIN |POLLOUT。在poll返回时我们可以检查revents中的标志对应于文件描述符请求的events结构体。如果POLLIN事件被设置则文件描述符可以被读取而不阻塞。如果POLLOUT被设置则文件描述符可以写入而不导致阻塞。这些标志并不是互斥的它们可能被同时设置表示这个文件描述符的读取和写入操作都会正常返回而不阻塞。
timeout参数指定等待的毫秒数无论I/O是否准备好poll都会返回。timeout指定为负数值表示无限超时使poll()一直挂起直到一个指定事件发生timeout为0指示poll调用立即返回并列出准备好I/O的文件描述符但并不等待其它的事件。这种情况下poll()就像它的名字那样一旦选举出来立即返回。
返回值和错误代码 成功时poll()返回结构体中revents域不为0的文件描述符个数如果在超时前没有任何事件发生poll()返回0失败时poll()返回-1并设置errno为下列值之一
EBADF 一个或多个结构体中指定的文件描述符无效。EFAULTfds 指针指向的地址超出进程的地址空间。EINTR 请求的事件之前产生一个信号调用可以重新发起。EINVALnfds 参数超出PLIMIT_NOFILE值。ENOMEM 可用内存不足无法完成请求。
2.3测出程序
编写一个echo server程序功能是客户端向服务器发送信息服务器接收输出并原样发送回给客户端客户端接收到输出到终端。
服务器端程序如下
1 #include stdio.h2 #include stdlib.h3 #include string.h4 #include errno.h5 6 #include netinet/in.h7 #include sys/socket.h8 #include poll.h9 #include unistd.h10 #include sys/types.h11 12 #define IPADDRESS 127.0.0.113 #define PORT 878714 #define MAXLINE 102415 #define LISTENQ 516 #define OPEN_MAX 100017 #define INFTIM -118 19 //函数声明20 //创建套接字并进行绑定21 static int socket_bind(const char* ip,int port);22 //IO多路复用poll23 static void do_poll(int listenfd);24 //处理多个连接25 static void handle_connection(struct pollfd *connfds,int num);26 27 int main(int argc,char *argv[])28 {29 int listenfd,connfd,sockfd;30 struct sockaddr_in cliaddr;31 socklen_t cliaddrlen;32 listenfd socket_bind(IPADDRESS,PORT);33 listen(listenfd,LISTENQ);34 do_poll(listenfd);35 return 0;36 }37 38 static int socket_bind(const char* ip,int port)39 {40 int listenfd;41 struct sockaddr_in servaddr;42 listenfd socket(AF_INET,SOCK_STREAM,0);43 if (listenfd -1)44 {45 perror(socket error:);46 exit(1);47 }48 bzero(servaddr,sizeof(servaddr));49 servaddr.sin_family AF_INET;50 inet_pton(AF_INET,ip,servaddr.sin_addr);51 servaddr.sin_port htons(port);52 if (bind(listenfd,(struct sockaddr*)servaddr,sizeof(servaddr)) -1)53 {54 perror(bind error: );55 exit(1);56 }57 return listenfd;58 }59 60 static void do_poll(int listenfd)61 {62 int connfd,sockfd;63 struct sockaddr_in cliaddr;64 socklen_t cliaddrlen;65 struct pollfd clientfds[OPEN_MAX];66 int maxi;67 int i;68 int nready;69 //添加监听描述符70 clientfds[0].fd listenfd;71 clientfds[0].events POLLIN;72 //初始化客户连接描述符73 for (i 1;i OPEN_MAX;i)74 clientfds[i].fd -1;75 maxi 0;76 //循环处理77 for ( ; ; )78 {79 //获取可用描述符的个数80 nready poll(clientfds,maxi1,INFTIM);81 if (nready -1)82 {83 perror(poll error:);84 exit(1);85 }86 //测试监听描述符是否准备好87 if (clientfds[0].revents POLLIN)88 {89 cliaddrlen sizeof(cliaddr);90 //接受新的连接91 if ((connfd accept(listenfd,(struct sockaddr*)cliaddr,cliaddrlen)) -1)92 {93 if (errno EINTR)94 continue;95 else96 {97 perror(accept error:);98 exit(1);99 }
100 }
101 fprintf(stdout,accept a new client: %s:%d\n, inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
102 //将新的连接描述符添加到数组中
103 for (i 1;i OPEN_MAX;i)
104 {
105 if (clientfds[i].fd 0)
106 {
107 clientfds[i].fd connfd;
108 break;
109 }
110 }
111 if (i OPEN_MAX)
112 {
113 fprintf(stderr,too many clients.\n);
114 exit(1);
115 }
116 //将新的描述符添加到读描述符集合中
117 clientfds[i].events POLLIN;
118 //记录客户连接套接字的个数
119 maxi (i maxi ? i : maxi);
120 if (--nready 0)
121 continue;
122 }
123 //处理客户连接
124 handle_connection(clientfds,maxi);
125 }
126 }
127
128 static void handle_connection(struct pollfd *connfds,int num)
129 {
130 int i,n;
131 char buf[MAXLINE];
132 memset(buf,0,MAXLINE);
133 for (i 1;i num;i)
134 {
135 if (connfds[i].fd 0)
136 continue;
137 //测试客户描述符是否准备好
138 if (connfds[i].revents POLLIN)
139 {
140 //接收客户端发送的信息
141 n read(connfds[i].fd,buf,MAXLINE);
142 if (n 0)
143 {
144 close(connfds[i].fd);
145 connfds[i].fd -1;
146 continue;
147 }
148 // printf(read msg is: );
149 write(STDOUT_FILENO,buf,n);
150 //向客户端发送buf
151 write(connfds[i].fd,buf,n);
152 }
153 }
154 }
客户端代码如下所示
1 #include netinet/in.h2 #include sys/socket.h3 #include stdio.h4 #include string.h5 #include stdlib.h6 #include poll.h7 #include time.h8 #include unistd.h9 #include sys/types.h
10
11 #define MAXLINE 1024
12 #define IPADDRESS 127.0.0.1
13 #define SERV_PORT 8787
14
15 #define max(a,b) (a b) ? a : b
16
17 static void handle_connection(int sockfd);
18
19 int main(int argc,char *argv[])
20 {
21 int sockfd;
22 struct sockaddr_in servaddr;
23 sockfd socket(AF_INET,SOCK_STREAM,0);
24 bzero(servaddr,sizeof(servaddr));
25 servaddr.sin_family AF_INET;
26 servaddr.sin_port htons(SERV_PORT);
27 inet_pton(AF_INET,IPADDRESS,servaddr.sin_addr);
28 connect(sockfd,(struct sockaddr*)servaddr,sizeof(servaddr));
29 //处理连接描述符
30 handle_connection(sockfd);
31 return 0;
32 }
33
34 static void handle_connection(int sockfd)
35 {
36 char sendline[MAXLINE],recvline[MAXLINE];
37 int maxfdp,stdineof;
38 struct pollfd pfds[2];
39 int n;
40 //添加连接描述符
41 pfds[0].fd sockfd;
42 pfds[0].events POLLIN;
43 //添加标准输入描述符
44 pfds[1].fd STDIN_FILENO;
45 pfds[1].events POLLIN;
46 for (; ;)
47 {
48 poll(pfds,2,-1);
49 if (pfds[0].revents POLLIN)
50 {
51 n read(sockfd,recvline,MAXLINE);
52 if (n 0)
53 {
54 fprintf(stderr,client: server is closed.\n);
55 close(sockfd);
56 }
57 write(STDOUT_FILENO,recvline,n);
58 }
59 //测试标准输入是否准备好
60 if (pfds[1].revents POLLIN)
61 {
62 n read(STDIN_FILENO,sendline,MAXLINE);
63 if (n 0)
64 {
65 shutdown(sockfd,SHUT_WR);
66 continue;
67 }
68 write(sockfd,sendline,n);
69 }
70 }
71 }
三、epoll
3.1基本知识
epoll是在2.6内核中提出的是之前的select和poll的增强版本。相对于select和poll来说epoll更加灵活没有描述符限制。epoll使用一个文件描述符管理多个描述符将用户关系的文件描述符的事件存放到内核的一个事件表中这样在用户空间和内核空间的copy只需一次。
3.2epoll接口
epoll操作过程需要三个接口分别如下
#include sys/epoll.h
int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
1 int epoll_create(int size); 创建一个epoll的句柄size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数给出最大监听的fd1的值。需要注意的是当创建好epoll句柄后它就是会占用一个fd值在linux下如果查看/proc/进程id/fd/是能够看到这个fd的所以在使用完epoll后必须调用close()关闭否则可能导致fd被耗尽。
2int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); epoll的事件注册函数它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数它不同与select()是在监听事件时告诉内核要监听什么类型的事件而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值第二个参数表示动作用三个宏来表示 EPOLL_CTL_ADD注册新的fd到epfd中 EPOLL_CTL_MOD修改已经注册的fd的监听事件 EPOLL_CTL_DEL从epfd中删除一个fd 第三个参数是需要监听的fd第四个参数是告诉内核需要监听什么事struct epoll_event结构如下
struct epoll_event {__uint32_t events; /* Epoll events */epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合
EPOLLIN 表示对应的文件描述符可以读包括对端SOCKET正常关闭 EPOLLOUT表示对应的文件描述符可以写 EPOLLPRI表示对应的文件描述符有紧急的数据可读这里应该表示有带外数据到来 EPOLLERR表示对应的文件描述符发生错误 EPOLLHUP表示对应的文件描述符被挂断 EPOLLET 将EPOLL设为边缘触发(Edge Triggered)模式这是相对于水平触发(Level Triggered)来说的。 EPOLLONESHOT只监听一次事件当监听完这次事件之后如果还需要继续监听这个socket的话需要再次把这个socket加入到EPOLL队列里
3 int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 等待事件的产生类似于select()调用。参数events用来从内核得到事件的集合maxevents告之内核这个events有多大这个maxevents的值不能大于创建epoll_create()时的size参数timeout是超时时间毫秒0会立即返回-1将不确定也有说法说是永久阻塞。该函数返回需要处理的事件数目如返回0表示已超时。
3.3.工作模式
epoll对文件描述符的操作有两种模式LTlevel trigger和ETedge trigger。LT模式是默认模式LT模式与ET模式的区别如下
LT模式当epoll_wait检测到描述符事件发生并将此事件通知应用程序应用程序可以不立即处理该事件。下次调用epoll_wait时会再次响应应用程序并通知此事件。
ET模式当epoll_wait检测到描述符事件发生并将此事件通知应用程序应用程序必须立即处理该事件。如果不处理下次调用epoll_wait时不会再次响应应用程序并通知此事件。
ET模式在很大程度上减少了epoll事件被重复触发的次数因此效率要比LT模式高。epoll工作在ET模式的时候必须使用非阻塞套接口以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
3.4.测试程序
编写一个服务器回射程序echo练习epoll过程。
服务器代码如下所示
1 #include stdio.h2 #include stdlib.h3 #include string.h4 #include errno.h5 6 #include netinet/in.h7 #include sys/socket.h8 #include arpa/inet.h9 #include sys/epoll.h10 #include unistd.h11 #include sys/types.h12 13 #define IPADDRESS 127.0.0.114 #define PORT 878715 #define MAXSIZE 102416 #define LISTENQ 517 #define FDSIZE 100018 #define EPOLLEVENTS 10019 20 //函数声明21 //创建套接字并进行绑定22 static int socket_bind(const char* ip,int port);23 //IO多路复用epoll24 static void do_epoll(int listenfd);25 //事件处理函数26 static void27 handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf);28 //处理接收到的连接29 static void handle_accpet(int epollfd,int listenfd);30 //读处理31 static void do_read(int epollfd,int fd,char *buf);32 //写处理33 static void do_write(int epollfd,int fd,char *buf);34 //添加事件35 static void add_event(int epollfd,int fd,int state);36 //修改事件37 static void modify_event(int epollfd,int fd,int state);38 //删除事件39 static void delete_event(int epollfd,int fd,int state);40 41 int main(int argc,char *argv[])42 {43 int listenfd;44 listenfd socket_bind(IPADDRESS,PORT);45 listen(listenfd,LISTENQ);46 do_epoll(listenfd);47 return 0;48 }49 50 static int socket_bind(const char* ip,int port)51 {52 int listenfd;53 struct sockaddr_in servaddr;54 listenfd socket(AF_INET,SOCK_STREAM,0);55 if (listenfd -1)56 {57 perror(socket error:);58 exit(1);59 }60 bzero(servaddr,sizeof(servaddr));61 servaddr.sin_family AF_INET;62 inet_pton(AF_INET,ip,servaddr.sin_addr);63 servaddr.sin_port htons(port);64 if (bind(listenfd,(struct sockaddr*)servaddr,sizeof(servaddr)) -1)65 {66 perror(bind error: );67 exit(1);68 }69 return listenfd;70 }71 72 static void do_epoll(int listenfd)73 {74 int epollfd;75 struct epoll_event events[EPOLLEVENTS];76 int ret;77 char buf[MAXSIZE];78 memset(buf,0,MAXSIZE);79 //创建一个描述符80 epollfd epoll_create(FDSIZE);81 //添加监听描述符事件82 add_event(epollfd,listenfd,EPOLLIN);83 for ( ; ; )84 {85 //获取已经准备好的描述符事件86 ret epoll_wait(epollfd,events,EPOLLEVENTS,-1);87 handle_events(epollfd,events,ret,listenfd,buf);88 }89 close(epollfd);90 }91 92 static void93 handle_events(int epollfd,struct epoll_event *events,int num,int listenfd,char *buf)94 {95 int i;96 int fd;97 //进行选好遍历98 for (i 0;i num;i)99 {
100 fd events[i].data.fd;
101 //根据描述符的类型和事件类型进行处理
102 if ((fd listenfd) (events[i].events EPOLLIN))
103 handle_accpet(epollfd,listenfd);
104 else if (events[i].events EPOLLIN)
105 do_read(epollfd,fd,buf);
106 else if (events[i].events EPOLLOUT)
107 do_write(epollfd,fd,buf);
108 }
109 }
110 static void handle_accpet(int epollfd,int listenfd)
111 {
112 int clifd;
113 struct sockaddr_in cliaddr;
114 socklen_t cliaddrlen;
115 clifd accept(listenfd,(struct sockaddr*)cliaddr,cliaddrlen);
116 if (clifd -1)
117 perror(accpet error:);
118 else
119 {
120 printf(accept a new client: %s:%d\n,inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);
121 //添加一个客户描述符和事件
122 add_event(epollfd,clifd,EPOLLIN);
123 }
124 }
125
126 static void do_read(int epollfd,int fd,char *buf)
127 {
128 int nread;
129 nread read(fd,buf,MAXSIZE);
130 if (nread -1)
131 {
132 perror(read error:);
133 close(fd);
134 delete_event(epollfd,fd,EPOLLIN);
135 }
136 else if (nread 0)
137 {
138 fprintf(stderr,client close.\n);
139 close(fd);
140 delete_event(epollfd,fd,EPOLLIN);
141 }
142 else
143 {
144 printf(read message is : %s,buf);
145 //修改描述符对应的事件由读改为写
146 modify_event(epollfd,fd,EPOLLOUT);
147 }
148 }
149
150 static void do_write(int epollfd,int fd,char *buf)
151 {
152 int nwrite;
153 nwrite write(fd,buf,strlen(buf));
154 if (nwrite -1)
155 {
156 perror(write error:);
157 close(fd);
158 delete_event(epollfd,fd,EPOLLOUT);
159 }
160 else
161 modify_event(epollfd,fd,EPOLLIN);
162 memset(buf,0,MAXSIZE);
163 }
164
165 static void add_event(int epollfd,int fd,int state)
166 {
167 struct epoll_event ev;
168 ev.events state;
169 ev.data.fd fd;
170 epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,ev);
171 }
172
173 static void delete_event(int epollfd,int fd,int state)
174 {
175 struct epoll_event ev;
176 ev.events state;
177 ev.data.fd fd;
178 epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,ev);
179 }
180
181 static void modify_event(int epollfd,int fd,int state)
182 {
183 struct epoll_event ev;
184 ev.events state;
185 ev.data.fd fd;
186 epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,ev);
187 }
客户端也用epoll实现控制STDIN_FILENO、STDOUT_FILENO、和sockfd三个描述符程序如下所示
1 #include netinet/in.h2 #include sys/socket.h3 #include stdio.h4 #include string.h5 #include stdlib.h6 #include sys/epoll.h7 #include time.h8 #include unistd.h9 #include sys/types.h10 #include arpa/inet.h11 12 #define MAXSIZE 102413 #define IPADDRESS 127.0.0.114 #define SERV_PORT 878715 #define FDSIZE 102416 #define EPOLLEVENTS 2017 18 static void handle_connection(int sockfd);19 static void20 handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf);21 static void do_read(int epollfd,int fd,int sockfd,char *buf);22 static void do_read(int epollfd,int fd,int sockfd,char *buf);23 static void do_write(int epollfd,int fd,int sockfd,char *buf);24 static void add_event(int epollfd,int fd,int state);25 static void delete_event(int epollfd,int fd,int state);26 static void modify_event(int epollfd,int fd,int state);27 28 int main(int argc,char *argv[])29 {30 int sockfd;31 struct sockaddr_in servaddr;32 sockfd socket(AF_INET,SOCK_STREAM,0);33 bzero(servaddr,sizeof(servaddr));34 servaddr.sin_family AF_INET;35 servaddr.sin_port htons(SERV_PORT);36 inet_pton(AF_INET,IPADDRESS,servaddr.sin_addr);37 connect(sockfd,(struct sockaddr*)servaddr,sizeof(servaddr));38 //处理连接39 handle_connection(sockfd);40 close(sockfd);41 return 0;42 }43 44 45 static void handle_connection(int sockfd)46 {47 int epollfd;48 struct epoll_event events[EPOLLEVENTS];49 char buf[MAXSIZE];50 int ret;51 epollfd epoll_create(FDSIZE);52 add_event(epollfd,STDIN_FILENO,EPOLLIN);53 for ( ; ; )54 {55 ret epoll_wait(epollfd,events,EPOLLEVENTS,-1);56 handle_events(epollfd,events,ret,sockfd,buf);57 }58 close(epollfd);59 }60 61 static void62 handle_events(int epollfd,struct epoll_event *events,int num,int sockfd,char *buf)63 {64 int fd;65 int i;66 for (i 0;i num;i)67 {68 fd events[i].data.fd;69 if (events[i].events EPOLLIN)70 do_read(epollfd,fd,sockfd,buf);71 else if (events[i].events EPOLLOUT)72 do_write(epollfd,fd,sockfd,buf);73 }74 }75 76 static void do_read(int epollfd,int fd,int sockfd,char *buf)77 {78 int nread;79 nread read(fd,buf,MAXSIZE);80 if (nread -1)81 {82 perror(read error:);83 close(fd);84 }85 else if (nread 0)86 {87 fprintf(stderr,server close.\n);88 close(fd);89 }90 else91 {92 if (fd STDIN_FILENO)93 add_event(epollfd,sockfd,EPOLLOUT);94 else95 {96 delete_event(epollfd,sockfd,EPOLLIN);97 add_event(epollfd,STDOUT_FILENO,EPOLLOUT);98 }99 }
100 }
101
102 static void do_write(int epollfd,int fd,int sockfd,char *buf)
103 {
104 int nwrite;
105 nwrite write(fd,buf,strlen(buf));
106 if (nwrite -1)
107 {
108 perror(write error:);
109 close(fd);
110 }
111 else
112 {
113 if (fd STDOUT_FILENO)
114 delete_event(epollfd,fd,EPOLLOUT);
115 else
116 modify_event(epollfd,fd,EPOLLIN);
117 }
118 memset(buf,0,MAXSIZE);
119 }
120
121 static void add_event(int epollfd,int fd,int state)
122 {
123 struct epoll_event ev;
124 ev.events state;
125 ev.data.fd fd;
126 epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,ev);
127 }
128
129 static void delete_event(int epollfd,int fd,int state)
130 {
131 struct epoll_event ev;
132 ev.events state;
133 ev.data.fd fd;
134 epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,ev);
135 }
136
137 static void modify_event(int epollfd,int fd,int state)
138 {
139 struct epoll_event ev;
140 ev.events state;
141 ev.data.fd fd;
142 epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,ev);
143 }
epoll既然是对select和poll的改进就应该能避免上述的三个缺点。那epoll都是怎么解决的呢在此之前我们先看一下epoll和select和poll的调用接口上的不同select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数epoll_create,epoll_ctl和epoll_waitepoll_create是创建一个epoll句柄epoll_ctl是注册要监听的事件类型epoll_wait则是等待事件的产生。
对于第一个缺点epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时在epoll_ctl中指定EPOLL_CTL_ADD会把所有的fd拷贝进内核而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次。对于第二个缺点epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中而只在epoll_ctl时把current挂一遍这一遍必不可少并为每个fd指定一个回调函数当设备就绪唤醒等待队列上的等待者时就会调用这个回调函数而这个回调函数会把就绪的fd加入一个就绪链表。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd利用schedule_timeout()实现睡一会判断一会的效果和select实现中的第7步是类似的。对于第三个缺点epoll没有这个限制它所支持的FD上限是最大可以打开文件的数目这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右具体数目可以cat /proc/sys/fs/file-max查看,一般来说这个数目和系统内存关系很大。
总结
1selectpoll实现需要自己不断轮询所有fd集合直到设备就绪期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表期间也可能多次睡眠和唤醒交替但是它是设备就绪时调用回调函数把就绪fd放入就绪链表中并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替但是select和poll在“醒着”的时候要遍历整个fd集合而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了这节省了大量的CPU时间。这就是回调机制带来的性能提升。
2selectpoll每次调用都要把fd集合从用户态往内核态拷贝一次并且要把current往设备等待队列中挂一次而epoll只要一次拷贝而且把current往等待队列上挂也只挂一次在epoll_wait的开始注意这里的等待队列并不是设备等待队列只是一个epoll内部定义的等待队列。这也能节省不少的开销。