epoll的最大好处在于他不会随着被监控描述符的数目的增长而导致效率极致下降。
select是遍历扫描来判断每个描述符是否有事件发生,当监控的描述付越多时,时间消耗就越多,并且由于系统的限制select最多可以监控1024个描述符。
epoll监控的描述符的数目很大,并且epoll对描述符的响应是触发的,即当有描述符有时间发生会有触发。
epoll模型有三个函数
epoll_create(int size);
epoll_wait(int epollfd,struct epoll_event *events,int maxevent,int timeout);
epoll_ctl(int epollfd,int op,struct epoll_event *events);
epoll_create(int size) 是创建size大小的文件描述符
epoll_wait()是用来等待事件的发生
epoll_ctl()是用来向内核注册,删除,修改一个文件描述符的
epoll模型有两种触发方式:一种是边缘触发,一种是水平触发。
eg:
1.进程A将通过pipe P的读端向描述符为fd以边缘触发加到自己的epoll监控里面,并用epoll_wait()等待事件发生
2.进程B往P中写2KB的数据;
3.进程A的描述符fd触发可读事件,因此epoll_wait()函数调用返回。
4.进程A从描述符fd上读取1KB的数据。
5.进程A调用epoll_wait()数据
根据边缘触发的特点:即使pipe P中还有1KB的数据可读,但是并不会触发可读事件,
6.进程B往P中写了2KB的数据
因为此时又有客户端发出请求,即监听套接口上又有新的数据到达,epoll_wait()函数会捕获fd上的可读事件。
解决方法是在ngx_http_read_request_header()函数,在开头加入return NGX_AGAIN 这样让Nginx不读走数据,在写数据的时候,或触发Nginx再次捕获可读信号。
测试代码:
#include <sys/types.h> #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> int main(int srgc,char *const *argv) { int sockfd; if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1) { fprintf(stderr,"socket fail:%s\n",strerror(errno)); return -1; } struct sockaddr_in server; bzero(&server,sizeof(server)); server.sin_family = AF_INET; server.sin_port = htons(80); if(!inet_aton("192.168.1.1",&server.sin_addr)) { fprintf(stderr,"Bad address %s\n",strerror(errno)); close(sockfd); return -1; } if(connect(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr)) == -1) { printf(stderr,"connect fail,%s\n",strerror(errno)); close(sockfd); return -1; } write(sockfd,"test1",sizeof("test1")); write(sockfd,"test2",sizeof("test2")); close(sockfd); return 0; }
使用边缘触发方式的epoll模型:
1.用于非阻塞文件描述符:即把待加入到epoll模型里的描述符都设置为no-block
2.只有当read()或write()返回EAGAIN,或者read()/write()读到/写到的数据长度小于请求的数据长度时,才需要挂起等待下一个时间的到来,否则会出现错误