IO多路复用之epoll总结epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。

、epoll接口

epoll操作过程需要三个接口,分别如下:

#include<sys/epoll.h>intepoll_create(intsize);intepoll_ctl(intepfd,intop,intfd,structepoll_event*event);intepoll_wait(intepfd,structepoll_event*events,intmaxevents,inttimeout);

(1)int epoll_create(int size);
  创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。

(2)int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件epoll的事件注册函数,它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。第一个参数是epoll_create()的返回值,第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd;
第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:

structepoll_event{__uint32_tevents;/*Epollevents*/epoll_data_tdata;/*Userdatavariable*/};

events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

(3) int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。

3、工作模式

  epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:

  LT模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。

  ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

  ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

4、测试程序

  编写一个服务器回射程序echo,练习epoll过程。

服务器代码如下所示:

#include<stdio.h>#include<stdlib.h>#include<string.h>#include<errno.h>#include<netinet/in.h>#include<sys/socket.h>#include<arpa/inet.h>#include<sys/epoll.h>#include<unistd.h>#include<sys/types.h>#defineIPADDRESS"127.0.0.1"#definePORT8787#defineMAXSIZE1024#defineLISTENQ5#defineFDSIZE1000#defineEPOLLEVENTS100//函数声明//创建套接字并进行绑定staticintsocket_bind(constchar*ip,intport);//IO多路复用epollstaticvoiddo_epoll(intlistenfd);//事件处理函数staticvoidhandle_events(intepollfd,structepoll_event*events,intnum,intlistenfd,char*buf);//处理接收到的连接staticvoidhandle_accpet(intepollfd,intlistenfd);//读处理staticvoiddo_read(intepollfd,intfd,char*buf);//写处理staticvoiddo_write(intepollfd,intfd,char*buf);//添加事件staticvoidadd_event(intepollfd,intfd,intstate);//修改事件staticvoidmodify_event(intepollfd,intfd,intstate);//删除事件staticvoiddelete_event(intepollfd,intfd,intstate);intmain(intargc,char*argv[]){intlistenfd;listenfd=socket_bind(IPADDRESS,PORT);listen(listenfd,LISTENQ);do_epoll(listenfd);return0;}staticintsocket_bind(constchar*ip,intport){intlistenfd;structsockaddr_inservaddr;listenfd=socket(AF_INET,SOCK_STREAM,0);if(listenfd==-1){perror("socketerror:");exit(1);}bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;inet_pton(AF_INET,ip,&servaddr.sin_addr);servaddr.sin_port=htons(port);if(bind(listenfd,(structsockaddr*)&servaddr,sizeof(servaddr))==-1){perror("binderror:");exit(1);}returnlistenfd;}staticvoiddo_epoll(intlistenfd){intepollfd;structepoll_eventevents[EPOLLEVENTS];intret;charbuf[MAXSIZE];memset(buf,0,MAXSIZE);//创建一个描述符epollfd=epoll_create(FDSIZE);//添加监听描述符事件add_event(epollfd,listenfd,EPOLLIN);for(;;){//获取已经准备好的描述符事件ret=epoll_wait(epollfd,events,EPOLLEVENTS,-1);handle_events(epollfd,events,ret,listenfd,buf);}close(epollfd);}staticvoidhandle_events(intepollfd,structepoll_event*events,intnum,intlistenfd,char*buf){inti;intfd;//进行选好遍历for(i=0;i<num;i++){fd=events[i].data.fd;//根据描述符的类型和事件类型进行处理if((fd==listenfd)&&(events[i].events&EPOLLIN))handle_accpet(epollfd,listenfd);elseif(events[i].events&EPOLLIN)do_read(epollfd,fd,buf);elseif(events[i].events&EPOLLOUT)do_write(epollfd,fd,buf);}}staticvoidhandle_accpet(intepollfd,intlistenfd){intclifd;structsockaddr_incliaddr;socklen_tcliaddrlen;clifd=accept(listenfd,(structsockaddr*)&cliaddr,&cliaddrlen);if(clifd==-1)perror("accpeterror:");else{printf("acceptanewclient:%s:%d\n",inet_ntoa(cliaddr.sin_addr),cliaddr.sin_port);//添加一个客户描述符和事件add_event(epollfd,clifd,EPOLLIN);}}staticvoiddo_read(intepollfd,intfd,char*buf){intnread;nread=read(fd,buf,MAXSIZE);if(nread==-1){perror("readerror:");close(fd);delete_event(epollfd,fd,EPOLLIN);}elseif(nread==0){fprintf(stderr,"clientclose.\n");close(fd);delete_event(epollfd,fd,EPOLLIN);}else{printf("readmessageis:%s",buf);//修改描述符对应的事件,由读改为写modify_event(epollfd,fd,EPOLLOUT);}}staticvoiddo_write(intepollfd,intfd,char*buf){intnwrite;nwrite=write(fd,buf,strlen(buf));if(nwrite==-1){perror("writeerror:");close(fd);delete_event(epollfd,fd,EPOLLOUT);}elsemodify_event(epollfd,fd,EPOLLIN);memset(buf,0,MAXSIZE);}staticvoidadd_event(intepollfd,intfd,intstate){structepoll_eventev;ev.events=state;ev.data.fd=fd;epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);}staticvoiddelete_event(intepollfd,intfd,intstate){structepoll_eventev;ev.events=state;ev.data.fd=fd;epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);}staticvoidmodify_event(intepollfd,intfd,intstate){structepoll_eventev;ev.events=state;ev.data.fd=fd;epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);}

客户端也用epoll实现,控制STDIN_FILENO、STDOUT_FILENO、和sockfd三个描述符,程序如下所示:

#include<netinet/in.h>#include<sys/socket.h>#include<stdio.h>#include<string.h>#include<stdlib.h>#include<sys/epoll.h>#include<time.h>#include<unistd.h>#include<sys/types.h>#include<arpa/inet.h>#defineMAXSIZE1024#defineIPADDRESS"127.0.0.1"#defineSERV_PORT8787#defineFDSIZE1024#defineEPOLLEVENTS20staticvoidhandle_connection(intsockfd);staticvoidhandle_events(intepollfd,structepoll_event*events,intnum,intsockfd,char*buf);staticvoiddo_read(intepollfd,intfd,intsockfd,char*buf);staticvoiddo_read(intepollfd,intfd,intsockfd,char*buf);staticvoiddo_write(intepollfd,intfd,intsockfd,char*buf);staticvoidadd_event(intepollfd,intfd,intstate);staticvoiddelete_event(intepollfd,intfd,intstate);staticvoidmodify_event(intepollfd,intfd,intstate);intmain(intargc,char*argv[]){intsockfd;structsockaddr_inservaddr;sockfd=socket(AF_INET,SOCK_STREAM,0);bzero(&servaddr,sizeof(servaddr));servaddr.sin_family=AF_INET;servaddr.sin_port=htons(SERV_PORT);inet_pton(AF_INET,IPADDRESS,&servaddr.sin_addr);connect(sockfd,(structsockaddr*)&servaddr,sizeof(servaddr));//处理连接handle_connection(sockfd);close(sockfd);return0;}staticvoidhandle_connection(intsockfd){intepollfd;structepoll_eventevents[EPOLLEVENTS];charbuf[MAXSIZE];intret;epollfd=epoll_create(FDSIZE);add_event(epollfd,STDIN_FILENO,EPOLLIN);for(;;){ret=epoll_wait(epollfd,events,EPOLLEVENTS,-1);handle_events(epollfd,events,ret,sockfd,buf);}close(epollfd);}staticvoidhandle_events(intepollfd,structepoll_event*events,intnum,intsockfd,char*buf){intfd;inti;for(i=0;i<num;i++){fd=events[i].data.fd;if(events[i].events&EPOLLIN)do_read(epollfd,fd,sockfd,buf);elseif(events[i].events&EPOLLOUT)do_write(epollfd,fd,sockfd,buf);}}staticvoiddo_read(intepollfd,intfd,intsockfd,char*buf){intnread;nread=read(fd,buf,MAXSIZE);if(nread==-1){perror("readerror:");close(fd);}elseif(nread==0){fprintf(stderr,"serverclose.\n");close(fd);}else{if(fd==STDIN_FILENO)add_event(epollfd,sockfd,EPOLLOUT);else{delete_event(epollfd,sockfd,EPOLLIN);add_event(epollfd,STDOUT_FILENO,EPOLLOUT);}}}staticvoiddo_write(intepollfd,intfd,intsockfd,char*buf){intnwrite;nwrite=write(fd,buf,strlen(buf));if(nwrite==-1){perror("writeerror:");close(fd);}else{if(fd==STDOUT_FILENO)delete_event(epollfd,fd,EPOLLOUT);elsemodify_event(epollfd,fd,EPOLLIN);}memset(buf,0,MAXSIZE);}staticvoidadd_event(intepollfd,intfd,intstate){structepoll_eventev;ev.events=state;ev.data.fd=fd;epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&ev);}staticvoiddelete_event(intepollfd,intfd,intstate){structepoll_eventev;ev.events=state;ev.data.fd=fd;epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,&ev);}staticvoidmodify_event(intepollfd,intfd,intstate){structepoll_eventev;ev.events=state;ev.data.fd=fd;epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&ev);}