1. 基本概念
当程序进行IO时,如果数据尚未准备好,那么IO将处于阻塞状态。当某个进程有多个打开的文件,比如socket,那么其后的所有准备好读写的文件将受到阻塞的影响而不能操作。不借助线程,单一进程无法在同一时间服务多个文件描述符。非阻挡式IO可以作为一个解决方案,但是效率并不高。首先进程需要不断发IO请求,其次,如果程序可以休眠,让出CPU将提高效率。多任务式IO是在其中任何一个文件描述符就绪时收到通知,此时IO将不会受到阻挡,其余时间处于休眠状态,将CPU资源让给别的进程。
为了实现I/O多路复用,epoll是在2.6内核中提出的,是之前的select和poll的增强版本。相对于select和poll来说,epoll更加灵活,没有描述符限制。epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次。相比于select,epoll最大的好处在于它不会随着监听fd数目的增长而降低效率。
2. api
#include <sys/epoll.h>
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);
- int epoll_create(int size) [创建句柄]
创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大。这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close()关闭,否则可能导致fd被耗尽。
函数生成一个epoll专用的文件描述符。它其实是在内核申请一空间,用来存放你想关注的socket fd上是否发生以及发生了什么事件。size就是你在这个epoll fd上能关注的最大socket fd数。随你定好了。只要你有空间。可参见上面与select之不同
- int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)[事件注册函数]
epoll的事件注册函数,对比select()函数,select()是在监听事件时告诉内核要监听什么类型的事件,而epoll是在调用epoll_ctl函数的时候,先注册要监听的事件类型。
epfd:epoll_create()的返回值;
op:表示要进行的操作,操作的动作使用了宏定义:
- EPOLL_CTL_ADD: 注册新的fd到epfd中;
- EPOLL_CTL_MOD: 修改已经注册的fd的监听事件;
- EPOLL_CTL_DEL: 删除epfd中的一个fd;
fd:关联的文件描述符,表示需要监听的fd;
event:指向epoll_event的指针,告诉内核需要监听什么事件,epoll_event的结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events描述事件类型,使用了宏定义:
1. EPOLLIN:表示对应的文件描述符可以读(包括对端socket正常关闭);
2. EPOLLOUT: 表示对应的文件描述符可以写;
3. EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(表示有外数据到来);
4. EPOLLHUP: 表示对应的文件描述符被挂断;
5. EPOLLET: 将EPOLL设置为边缘触发(Edge Triggered)模式;
6. EPOLLONESHOT: 只监听一次事件,当监听玩这次事件之后,如果还需要继续监听,需要再次将该socket加入EPOLL队列中。
- 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工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger,水平触发)和ET(edge trigger,边缘触发)。
LT模式:水平触发是缺省的工作方式。当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件(进行IO操作)。下次调用epoll_wait时,会再次响应应用程序并通知此事件。由于对于描述事件符在处理前会进行多次通知,因此出错的概率小;
ET模式:当epoll_wait检测到描述符事件发生并将此事件通知应用程序,此时它会假设你知道文件描述符已就绪,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件,直到做了一定操作导致该文件描述符再次变为未就绪状态。但是如果一直对该fd进行IO操作()
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。