标签:epoll
一、对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
标签:epoll
原文地址:http://10548195.blog.51cto.com/10538195/1828197