一、对epoll的初步认识
epoll是为了处理大量句柄而对poll做了改进。
epoll的相关系统调用:
(1)iint epoll_create(int size)
创建一个epoll的句柄。size通常是被忽略的。当创建epoll句柄后,它就会占用一个fd值,所以在使用完epoll后,必须调用close()进行关闭,否则可能导致fd被耗尽。
(2)int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
epoll的事件注册函数,它不同于select()是在监视事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值;
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
EPOLLIN:表示对应的文件描述符可以读。
EPOLLOUT:表示对应的文件描述符可以写。
EPOLLPRI:表示对应的文件描述符有紧急的数据可读。
EPOLLERR:表示对应的文件描述符发生错误。
EPOLLHUP:表示对应的文件描述符被挂断。
EPOLLLET:将EPOLL设为边缘触发模式(ET)。这里是相对于水平触发(LT)来说的。
EPOLLONESHOT:只监听一次事件。当监听完这个事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到epoll队列中。
(3)int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)
收集在epoll监控的事件中已经发送的事件。参数events是分配好的epoll_event结构体数组。epoll将会把发生的事件赋值到events数组中(evevts不可以是空指针,内核只负责把数据复制到evevts数组中,并不会帮助我们在用户态中分配内存)。maxevents告知内核这个events有多大,这个maxevents的值不能大于epoll_create()时的size。参数timeout是超时时间,如果函数调用成功,返回对应I/O已准备好的文件描述符数目,如果返回0表示已超时。
二、epoll的工作原理
epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表描述符数量的值,我们只需要在epoll指定的一个数组中依次取得相应数量的文件描述符即可。这里也使用了内存映射技术,这里也省掉了这些描述符在系统调用时复制的开销。
epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl的回掉机制,迅速激活这个文件描述符,当进程调用epoll_wait时便得到通知。
三、水平触发(LT)和边缘触发 (ET)
(1)Level Triggered工作模式
以LT调用epoll接口的时候,就相当于一个速度比较快的poll(2)。LT是epoll的缺省工作方式,同时支持阻塞和非阻塞。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后再对这个就绪的fd进行IO操作,当不做任何操作的时候,内核还是会继续通知你的。所以,这种模式编程出错的可能性要小一些,传统的select/poll就是这种模型的代表。
代码描述:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/epoll.h>
4 #include<sys/types.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7 #include<arpa/inet.h>
8 #include<unistd.h>
9 #include<fcntl.h>
10 #include<assert.h>
11 #include<errno.h>
12 #include<string.h>
13
14 #define _BACKLOG_ 5
15 #define _SIZE_ 64
16 #define _MAX_FD_SIZE_ 64
17 #define _BUF_SIZE_ 10240
18
19 typedef struct data_buf
20 {
21 int fd;
22 char buf[_BUF_SIZE_];
23 }data_buf_t,*data_buf_p;
24
25 static void Usage(char* const proc)
26 {
27 assert(proc);
28 printf("%s [ip][port]\n",proc);
29 }
30
31 static int startup(char *ip,int port)
32 {
33 assert(ip);
34 int sock=socket(AF_INET,SOCK_STREAM,0);
35 if(sock<0)
36 {
37 perror("socket");
38 exit(1);
39 }
40
41 int opt=1;
42 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
43
44 struct sockaddr_in local;
45 local.sin_family=AF_INET;
46 local.sin_port=htons(port);
47 local.sin_addr.s_addr=inet_addr(ip);
48
49 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
50 {
51 perror("bind");
52 exit(2);
53 }
54 if(listen(sock,_BACKLOG_)<0)
55 {
56 perror("listen");
57 exit(3);
58 }
59 return sock;
60 }
61
62 //sock=listen_sock
63 static int server_epoll(int sock)
64 {
65 int epoll_fd=epoll_create(_SIZE_);
66 if(epoll_fd<0)
67 {
68 perror("epoll_create");
69 return -1;
70 }
71
72 struct epoll_event ev;
73 ev.data.fd=sock;
74 ev.events=EPOLLIN;
75 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev)<0)
76 {
77 perror("epoll_ctl");
78 return -2;
79 }
80 struct epoll_event ev_out[_MAX_FD_SIZE_];
81 int max=_MAX_FD_SIZE_;
82
83 int timeout=5000;
84 int i=0;
85 int num=-1;
86 while(1)
87 {
88 switch(num=epoll_wait(epoll_fd,ev_out,max,timeout))
89 {
90 case -1://errno
91 perror("epoll_wait");
92 break;
93 case 0://timeout
94 printf("timeout...\n");
95 break;
96 default://data ready
97 {
98 for(i=0;i<num;++i)
99 {
100 //get a new connect
101 if(ev_out[i].data.fd==sock&&ev_out[i].events & EPOLL IN)
102 {
103 struct sockaddr_in client;
104 socklen_t len=sizeof(client);
105
106 int fd=ev_out[i].data.fd;
107 int new_sock=accept(fd,(struct sockaddr*)&client ,&len);
108 if(new_sock<0)
109 { 110 perror("accept");
111 printf("%s:%d\n",strerror(errno),new_sock);
112 continue;
113 }
114 ev.events=EPOLLIN;
115 ev.data.fd=new_sock;
116 epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
117 printf("get a new connect...\n");
118 }
119 else if(ev_out[i].data.fd>0&&ev_out[i].events & EPOL LIN)
120 {
121 int fd=ev_out[i].data.fd;
122 data_buf_p mem=(data_buf_p)malloc(sizeof(data_bu f_t));
123 if(mem==NULL)
124 {
125 continue;
126 }
127 mem->fd=fd;
128 int _s=read(mem->fd,mem->buf,sizeof(mem->buf)); 129 if(_s<0)
130 {
131 perror("read");
132 close(fd);
133 free(mem);
134 }
135 else if(_s==0)
136 {
137 printf("client close...\n");
138 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
139 close(fd);
140 free(mem);
141 }
142 else if(_s>0)
143 {
144 mem->buf[_s]=‘\0‘;
145 printf("client say:%s\n",mem->buf);
146 ev.events=EPOLLOUT;
147 ev.data.ptr=mem;
148 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,mem->fd,&ev ); 149 }
150 else
151 {
152 continue;
153 }
154 }
155
156 else if(ev_out[i].data.fd>0&&ev_out[i].events & EPOL LOUT)
157 {
158 data_buf_p mem=(data_buf_p)ev_out[i].data.ptr;
159 ssize_t _s=write(mem->fd,mem->buf,sizeof(mem->bu f));
160 close(mem->fd);
161 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NULL);
162 }
173 else
174 {}
175 }
176 break;
177 }
178 }
179 }
180 }
181
182 int main(int argc,char *argv[])
183 {
184 if(argc!=3)
185 {
186 Usage(argv[0]);
187 exit(1);
188 }
189 char *ip=argv[1];
190 int port=atoi(argv[2]);
191 int listen_sock=startup(ip,port);
192 server_epoll(listen_sock);
193 close(listen_sock);
194 return 0;
195 }运行结果:
用telnet测试:
用浏览器测试,则是:
若修改代码为:
164 else if(ev_out[i].data.fd>0&&ev_out[i].events & EPOLLOUT)
165 {
166
167 char *msg="HTTP/1.0 200 OK\r\n\r\nhello worl d\r\n";
168 data_buf_p mem=(data_buf_p)ev_out[i].data.pt r;
169 ssize_t _s=write(mem->fd,msg,strlen(msg));
170 close(mem->fd);
171 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NUL L);
172 } 如果将文件句柄添加到epoll描述符的时候使用了EPOLLET标志,那么在调用epoll_wait(2)之后就有可能会被挂起,因为剩余的数据还存在于文件的输入缓冲区中,而且数据发出端还在等待一个针对已经发出的数据的反馈信息。只有在监视的文件句柄上发生了某个事件的时候ET工作模式才会汇报事件。因此在epoll_wait(2)的时候,调用者可能会放弃等待仍存在于文件输入缓冲区内的剩余数据。因此最好以下面的方式调用ET模式:
a、基于非阻塞文件句柄
b、只有当read(2)或write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()时都需要循环读,直到读到一个EAGAIN时才认为此次时间处理完成。当read()返回的读到的数据长度小于请求的数据长度时,就可以确定此时缓冲区中没有数据了。也就可以认为此事件已处理完成。
代码描述:
1 #include<stdio.h>
2 #include<stdlib.h>
3 #include<sys/epoll.h>
4 #include<sys/types.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7 #include<arpa/inet.h>
8 #include<unistd.h>
9 #include<fcntl.h>
10 #include<assert.h>
11 #include<errno.h>
12 #include<string.h>
13
14 #define _BACKLOG_ 5
15 #define _SIZE_ 256
16 #define _MAX_FD_SIZE_ 64
17 #define _BUF_SIZE_ 10240
18
19 typedef struct data_buf
20 {
21 int fd;
22 char buf[_BUF_SIZE_];
23 }data_buf_t,*data_buf_p;
24
25 static int set_non_block(int fd)
26 {
27 int old_fl=fcntl(fd,F_GETFL);
28 if(old_fl<0)
29 {
30 perror("fcntl");
31 return -1;
32 }
33 if(fcntl(fd,F_SETFL,old_fl|O_NONBLOCK))
34 {
35 perror("fcntl");
36 return -2;
37 }
38 return 0;
39 }
40
41 static void Usage(char* const proc)
42 {
43 assert(proc);
44 printf("%s [ip][port]\n",proc);
45 }
46
47 static int startup(char *ip,int port)
48 {
49 assert(ip);
50 int sock=socket(AF_INET,SOCK_STREAM,0);
51 if(sock<0)
52 {
53 perror("socket");
54 exit(1);
55 }
56
57 int opt=1;
58 setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
59
60 struct sockaddr_in local;
61 local.sin_family=AF_INET;
62 local.sin_port=htons(port);
63 local.sin_addr.s_addr=inet_addr(ip);
64
65 if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
66 {
67 perror("bind");
68 exit(2);
69 }
70 if(listen(sock,_BACKLOG_)<0)
71 {
72 perror("listen");
73 exit(3);
74 }
75 return sock;
76 }
77
78
79 int read_data(int fd,char *buf,int size)
80 {
81 assert(buf);
82 memset(buf,‘\0‘,size);
83 int index=0;
84 ssize_t _s=-1;
85 while(_s=read(fd,buf+index,size-index)<size)
86 {
87 if(errno==EAGAIN)
88 {
89 break;
90 }
91 index+=_s;
92 }
93 return index;
94 }
95
96 int write_data(int fd,char *buf,int size)
97 {
98 int index=0;
99 ssize_t _s=-1;
100 while(_s=write(fd,buf+index,size-index)<size)
101 {
102 if(errno==EAGAIN)
103 {
104 break;
105 }
106 index+=_s;
107 }
108 return index;
109 }
110
111 //sock=listen_sock
112 static int server_epoll(int sock)
113 {
114 int epoll_fd=epoll_create(_SIZE_);
115 if(epoll_fd<0)
116 {
117 perror("epoll_create");
118 return -1;
119 }
120
121 struct epoll_event ev;
122 ev.data.fd=sock;
123 ev.events=EPOLLIN|EPOLLET;
124 if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev)<0)
125 {
126 perror("epoll_ctl");
127 return -2;
128 }
129 struct epoll_event ev_out[_MAX_FD_SIZE_];
130 int max=_MAX_FD_SIZE_;
131
132 int timeout=5000;
133 int i=0;
134 int num=-1;
135 int done=0;
136 while(!done)
137 {
138 switch(num=epoll_wait(epoll_fd,ev_out,max,timeout))
139 {
140 case -1://errno
141 perror("epoll_wait");
142 break;
143 case 0://timeout
144 printf("timeout...\n");
145 break;
146 default://data ready
147 {
148 for(i=0;i<num;++i)
149 {
150 //get a new connect
151 if(ev_out[i].data.fd==sock&&(ev_out[i].events&EPOLLI N))
152 {
153 struct sockaddr_in client;
154 socklen_t len=sizeof(client);
155
156 int fd=ev_out[i].data.fd;
157 int new_sock=accept(fd,(struct sockaddr*)&client ,&len); 158 if(new_sock<0)
159 {
160 perror("accept");
161 printf("%s:%d\n",strerror(errno),new_sock);
162 continue;
163 }
164 set_non_block(new_sock);
165 ev.events=EPOLLIN|EPOLLET;
166 ev.data.fd=new_sock;
167 epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
168 printf("get a new connect...\n");
169 }
170 else
171 {
172 //read events ready
173 if(ev_out[i].events&EPOLLIN)
174 {
175 int fd=ev_out[i].data.fd;
176 data_buf_p mem=(data_buf_p)malloc(sizeof(dat a_buf_t));
177 if(mem==NULL)
178 {
179 perror("malloc");
180 continue;
181 }
182 //read data all done...
183 mem->fd=fd;
184 ssize_t _s=read_data(mem->fd,mem->buf,sizeof (mem->buf));
185 if(_s>0)
186 {
187 (mem->buf)[_s]=‘\0‘;
188 ev.data.ptr=mem;
189 printf("client:%s\n",mem->buf);
190 ev.events=EPOLLOUT|EPOLLET;
191 ev.data.ptr=mem;
192 epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev) ;
193 }
194 else if(_s==0)
195 {
196 printf("client close...\n");
197 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL );
198 close(fd);
199 free(mem);
200 }
201 else
202 {
203 continue;
204 }
205 }
206 else if(ev_out[i].events&EPOLLOUT)
207 {
208 data_buf_p mem=(data_buf_p)ev_out[i].data.pt r;
209 write_data(mem->fd,mem->buf,strlen(mem->buf) );
210 close(mem->fd);
211 epoll_ctl(epoll_fd,EPOLL_CTL_DEL,mem->fd,NUL L);
212 free(mem);
213 }
214 else
215 {
216 }
217 }
218 }
219 }
220 break;
221 }
222 }
223 }
224
225 int main(int argc,char *argv[])
226 {
227 if(argc!=3)
228 {
229 Usage(argv[0]);
230 exit(1);
231 }
232 char *ip=argv[1];
233 int port=atoi(argv[2]);
234 int listen_sock=startup(ip,port);
235 server_epoll(listen_sock);
236 close(listen_sock);
237 return 0;
238 }
239 运行结果:
epoll首先调用epoll_create建立一个epoll对象,参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。
epoll_ctl可以操作上面的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它等等。
epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。
因此可以看出epoll优于select/poll:因为后者每次调用时都要传递你所要监控的所有socket给socket/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄每次都要copy几十几百KB的内存到内核态,非常低效。而调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。
本文出自 “zwy” 博客,请务必保留此出处http://10548195.blog.51cto.com/10538195/1828197
原文:http://10548195.blog.51cto.com/10538195/1828197