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()讀到/寫到的數據長度小於請求的數據長度時,才需要掛起等待下一個時間的到來,否則會出現錯誤