Linux epoll总结
Linux epoll
epoll是Kernel 2.6后新加入的事件机制,在高并发条件下,远优于select。epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。因为在内核中的select实现中,它是采用轮询来处理的,轮询的fd数目越多,自然耗时越多。并且,在linux/posix_types.h头文件有这样的声明:
#define __FD_SETSIZE 1024 //select最多同时监听1024个fd当然,可以通过修改头文件再重编译内核来扩大这个数目,但这似乎并不治本。
所以在Nginx中采用了epoll来实现其高并发特性。
工作方式
LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的selectpoll都是这种模型的代表。
ET(edge-triggered):边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。
区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。
主要的数据结构
epoll_event的结构如下:
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t;//保存触发事件的某个文件描述符相关的数据 struct epoll_event { __uint32_t events; /* epoll event */ epoll_data_t data; /* User data variable */ };
events表示感兴趣的事件和被触发的事件,可能的取值为:
EPOLLIN | 对应的文件描述符可以读 |
EPOLLOUT | 对应的文件描述符可以写 |
EPOLLPRI | 对应的文件描述符有紧急的数可读 |
EPOLLERR | 对应的文件描述符发生错误 |
EPOLLHUP | 对应的文件描述符被挂断 |
EPOLLET | 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的 |
EPOLLONESHOT | 只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里 |
操作函数
epoll的接口非常简单,用三个相关函数来创建epoll句柄、注册epoll事件以及等待事件的发生。
创建epoll句柄:
int epoll_create(int size); //size表示内核需要监听的数目
//return : epoll文件描述符
需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值(文件标识符),在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
epoll事件注册函数:
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //epfd是epoll_create()的返回值 //op表示动作 /* op可被表示为: EPOLL_CTL_ADD:注册新的fd到epfd中; EPOLL_CTL_MOD:修改已经注册的fd的监听事件; EPOLL_CTL_DEL:从epfd中删除一个fd; */ //fd是需要监听的fd //event是内核需要监听的事件
等待事件发生函数:
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout) //epfd是函数返回值 //events是内核监听事件的集合 //maxevents是epoll_wait可以处理的连接事件的最大限度值 //timeout是超时时间
//返回值:请求数
epoll工作流程
首先,需要调用epoll_create创建epoll,此后我们就可以进行socket/bind/listen,然后调用epoll_ctl进行注册。接下来,就可以通过一个while(1)循环调用epoll_wait来等待事件的发生,然后循环查看接收到的事件并进行处理。如果事件是sever的socketfd我们就要进行accept,并且把接收到client的socketfd加入到要监听的事件中。如果在监听过程中,需要修改操作方式(读/写),可以调用epoll_ctl来重新修改。如果监听到某一个客户端关闭,那么我就需要再次调用epoll_ctl把它从epoll监听事件中删除。
实例
1 //epoll_server.c 2 #include <stdio.h> 3 #include <string.h> 4 #include <sys/socket.h> 5 #include <sys/epoll.h> 6 #include <errno.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include <sys/types.h> 10 #include <unistd.h> 11 12 #define SERV_PORT 8802 13 14 15 int main() 16 { 17 int i,flag; 18 int sockfd,clntfd,newfd; 19 int epfd,nfds; 20 ssize_t n; 21 char buffer[1024]; 22 int s = sizeof(struct sockaddr); 23 24 struct sockaddr_in serv_addr; 25 struct sockaddr_in clnt_addr; 26 //定义epoll数据结构 27 struct epoll_event ev,events[20]; 28 29 epfd = epoll_create(256); 30 31 //创建socket,并初始化事件ev 32 sockfd = socket(AF_INET, SOCK_STREAM, 0); 33 if(sockfd < 0){ 34 perror("socket error! "); 35 return -1; 36 } 37 ev.data.fd = sockfd; 38 ev.events = EPOLLIN|EPOLLET; 39 40 //注册epoll事件 41 flag = epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev); 42 if(flag < 0){ 43 perror("epoll_ctl error! "); 44 return -1; 45 } 46 47 bzero(&serv_addr, sizeof(serv_addr)); 48 serv_addr.sin_family = AF_INET; 49 serv_addr.sin_port = htons(SERV_PORT); 50 serv_addr.sin_addr.s_addr = htonl( INADDR_ANY ); 51 52 flag = bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(struct sockaddr)); 53 if(flag < 0){ 54 perror("bind error! "); 55 return -1; 56 } 57 printf("bind "); 58 59 flag = listen(sockfd, 20); 60 if(flag < 0){ 61 perror("listen error! "); 62 return -1; 63 } 64 printf("listen "); 65 66 //开始循环 67 while(1){ 68 //等待事件发生,返回请求数目 69 nfds = epoll_wait(epfd, events, 20, 500); 70 //一次处理请求 71 for(i = 0; i < nfds; ++i){ 72 if(events[i].data.fd == sockfd){ 73 clntfd = accept(sockfd, (struct sockaddr*)&clnt_addr,(unsigned int*)&s); 74 if(clntfd < 0){ 75 perror("accept error"); 76 continue; 77 } 78 printf("accept "); 79 80 char *str = inet_ntoa(clnt_addr.sin_addr); 81 printf("accepnt the client ip : %s ",str); 82 83 //设置文件标识符,设置操作属性:写操作 84 ev.data.fd = clntfd; 85 ev.events = EPOLLOUT|EPOLLET; 86 //向创建的的epoll进行注册写操作 87 epoll_ctl(epfd, EPOLL_CTL_ADD, clntfd, &ev); 88 } 89 else if(events[i].events&EPOLLOUT){ 90 printf("EPOLLOUT "); 91 92 if((newfd = events[i].data.fd) < 0) 93 continue; 94 bzero(buffer,sizeof(buffer)); 95 strcpy(buffer,"welcome to myserver! "); 96 flag = send(newfd, buffer, 1024, 0); 97 if(flag < 0){ 98 perror("send error"); 99 continue; 100 } 101 //修改操作为读操作 102 ev.data.fd = clntfd; 103 ev.events = EPOLLIN|EPOLLET; 104 epoll_ctl(epfd, EPOLL_CTL_MOD, newfd, &ev); 105 } 106 else if(events[i].events&EPOLLIN){ 107 printf("EPOLLIN "); 108 109 bzero(buffer,sizeof(buffer)); 110 if((newfd = events[i].data.fd) < 0) 111 continue; 112 if((n = read(newfd, buffer, 1024)) < 0){ 113 if(errno == ECONNRESET){ 114 close(newfd); 115 events[i].data.fd = -1; 116 printf("errno ECONRESET! "); 117 } 118 else{ 119 perror("readbuffer error! "); 120 } 121 } 122 else if(n == 0){//表示客户端已经关闭 123 close(newfd); 124 events[i].data.fd = -1; 125 printf("n为0 "); 126 } 127 if(buffer[0]!='0') 128 printf("have read: %s ",buffer); 129 } 130 } 131 } 132 close(sockfd); 133 return 0; 134 }
源码下载:这里
参考
http://www.cnblogs.com/venow/archive/2012/11/30/2790031.html
http://hi.baidu.com/jingweiyoung/item/ae9fc81714be67dbbf9042b9
http://www.linuxidc.com/Linux/2011-04/35156p3.htm
http://www.cppblog.com/converse/archive/2008/04/29/48482.html