一、多路復用之——epoll
int
epoll_create(int size);
int
epoll_ctl(int epfd,int op,int fd,struct epoll_event* event);
int
epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
1、epoll_create:創建一個epoll句柄,size參數可以忽略
當創建一個句柄後,會占用一個fd值,故在使用完後,要close(fd)。
2、epoll_ctl:注冊要監聽的事件類型
參數:(1)epfd:epoll_create的返回值;
(2)op:EPOLL_CTL_ADD
注冊新fd到epfd中
EPOLL_CTL_MOD
修改已經注冊的fd
EPOLL_CTL_DEL 從epfd中刪除一個fd
(3)fd:要監聽的fd;
(4)event:監聽的事件;
struct epoll_event{
_uint32_t events;
epoll_data_t data;
}
typedef union epoll_data{
void* ptr;
int fd;
_uint32_t U32;
_uint64_t U64;
}
(5)
events集合:
EPOLLIN:表示對應的文件描述符可以讀;
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急事件可讀;
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET:將EPOLL設為邊緣觸發模式;
EPOLLLT:將EPOLL設為水平觸發模式;
EPOLLONESHOT:只監聽一次事件;
3、epoll_wait:收集在epoll監控的事件中已經發生的事件
參數:(1)epfd:創建的epoll句柄;
(2)events:已分配好的epoll_events結構體,epoll會將發生的事件放到events中;
(3)maxevents:events的大小;
(4)timeout:NULL:沒有timeout,一直阻塞等待知道某個事件發生;
0:僅檢測描述符集合的狀態,然後立即返回;
特定值:等待timeout時間,如果沒有發生,超時返回。
4、epoll的兩種工作模式:
epoll工作在ET模式的時候,必須使用非阻塞套接口,以避免由於一個文件句柄的阻塞讀、阻塞寫操作把處理多個文件描述符的任務餓死。
epoll工作在LT模式的時候,在收到多個數據的時候仍然會產生多個事件,支持阻塞和非阻塞接口,這樣,內核告訴你一個文件描述符是否就緒了,然後你可以進行I/O,若你不做任何操作,內核還是會繼續通知你的,錯誤率小。
ET與LT的區別在於,當一個新事件到來時,ET模式下當然可以從epoll_wait調用中獲取到這個事件,可是如果這次沒有把這個事件對應的套接字緩沖區處理完,在這個套接字中沒有新事件到來時,ET模式下無法再次獲取數據。但LT正好相反,只要一個事件對應的套接字緩沖區還有數據,就能夠獲取。
5、epoll模型的優點:
(1)使用內存映射(mmap)技術,避免用戶到內存的拷貝;
(2)epoll事先通過epoll_ctl()來注冊一個文件描述符,一旦基於某個文件描述符就緒時,內核會采用類似callback的回調機制,迅速激活這個文件描述符,當進程調用epoll_wait時使得到通知;
(3)監視的文件描述符數量不受限制,它所支持的fd上限是最大可以打開文件的數目;
(4)I/O的效率不會隨著fd數量的增加而下降,select、poll實現需要自己不斷輪詢所有fd集合,指到設備就緒,期間可能要睡眠和喚醒多次交替。而epoll其實也需要調用epoll_wait不斷輪詢就緒鏈表,期間也可能睡眠和喚醒多次交替,但是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,但select和poll在“醒著”的時候要遍歷整個fd集合,而epoll在“醒著”的時候只需要遍歷就緒鏈表是否為空就行了。這節省了大量的CPU時間。這就是回調機制的性能提升。
代碼示例:
epoll_server.c
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<errno.h> #include<netinet/in.h> #include<arpa/inet.h> #include<sys/epoll.h> #include<sys/socket.h> #include<sys/types.h> #include<fcntl.h> #define MAX_NUM 64 int startup(int port) { int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0){ perror("socket"); exit(1); } struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(port); local.sin_addr.s_addr=htonl(INADDR_ANY); if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){ perror("bind"); exit(2); } if(listen(sock,5)<0){ perror("listen"); exit(3); } return sock; } //set file descriptor for no blocking mode void set_nonoblock(int _fd) { int fl=fcntl(_fd,F_GETFL); if(fl<0){ perror("fcntl"); return; } fcntl(_fd,fl | O_NONBLOCK); return; } //recv data int read_data(int _fd,char* buf,int len) { ssize_t _size=-1; int total=0; while((_size=recv(_fd,buf+total,len-1,MSG_DONTWAIT))){ if(_size>0){ total+=_size; }else if(_size==0){//file end return 0; }else{ return -1; } } } int main() { short port=8080; int listen_sock=startup(port); struct sockaddr_in client; socklen_t len=sizeof(client); int epoll_fd=epoll_create(256);//create epoll handle int timeout=1000; if(epoll_fd<0){ perror("epoll_create"); exit(1); } struct epoll_event _ev;//save care fd _ev.events=EPOLLIN; _ev.data.fd=listen_sock; if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&_ev)<0){ perror("epoll_ctl"); goto LABLE; } struct epoll_event _ev_out[MAX_NUM];//save ready fd char buf[1024*5]; memset(buf,'\0',sizeof(buf)); int ready_num=-1;//save ready_fd num while(1){ switch(ready_num=epoll_wait(epoll_fd,_ev_out,MAX_NUM,timeout)){ case 0: printf("timeout\n"); break; case -1: perror("epoll_wait"); break; default: { int i=0; for(;i<ready_num;i++){ int _fd=_ev_out[i].data.fd; //listen_sock if(_fd==listen_sock && (_ev_out[i].events & EPOLLIN)){ int new_sock=accept(_fd,(struct sockaddr*)&client,&len); if(new_sock<0){ perror("accept"); continue; } printf("get a new connect...\n"); set_nonoblock(new_sock); _ev.events=EPOLLIN | EPOLLET; _ev.data.fd=new_sock; if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&_ev)<0){ perror("epoll_ctl"); close(new_sock); continue; } continue; } //data_sock if(_ev_out[i].events & EPOLLIN){ if(read_data(_fd,buf,sizeof(buf))==0){ printf("client close..."); epoll_ctl(epoll_fd,EPOLL_CTL_DEL,_fd,NULL); } printf("%s\n",buf); } } } break; } } LABLE: close(epoll_fd); return 0; }Makefile:
epoll_server:epoll_server.c gcc -o $@ $^ .PHONY:clean clean: rm -rf epoll_server