和之前学过的两种多路复用方法 select、poll相比,epoll的性能最好。
epoll的用法
epoll通过监听注册的多个文件描述符,来进行I/O事件的分发处理。和poll不同的是,epoll不仅提供了默认的 level-triggered(条件触发)机制,还提供了性能更好的 edge-triggered (边缘触发)机制。
epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无须像select和poll那样每次调用都要重复传入文件描述符集或事件集。(epoll实例就是这个事件表)
为了使用epoll进行网络编程,通常需要以下几步:1)epoll_create,2)epoll_ctl,3)epoll_wait
epoll_create
函数原型
int epoll_create(int size);
int epoll_create1(int flags);
通过epoll_create()函数,可以创建一个epoll实例,从Linux2.6.8开始,参数size被自动忽略,但是该值仍然需要一个大于0的整数。size参数只是用来给内核一个提示,告诉它事件表需要多大。
这个epoll实例被用来调用 epoll_ctl 和 epoll_wait,当这个epoll实例不再需要时,比如服务器正常关机,那么需要调用close()函数释放epoll实例,让系统内核回收epoll实例所分配使用的内核资源。
关于这个参数 size,在一开始的 epoll_create 实现中,是用来告知内核期望监控的文件描述字大小,然后内核使用这部分的信息来初始化内核数据结构,在新的实现中,这个参数不再被需要,因为内核可以动态分配需要的内核数据结构。我们只需要注意,每次将 size 设置成一个大于 0 的整数就可以了。
epoll_create1()的用法和epoll()基本一致,当输入的flags为0时,则和epoll_create()一样,内核自动忽略。
返回值
若成功,则返回一个大于0的值,表示epoll实例;失败则返回-1。
epoll_ctl
函数原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
创建完epoll实例后,通过调用epoll_ctl 往这个epoll实例增加或删除监控的事件。
参数
1)epfd参数是刚刚调用epoll_create()创建的epoll实例描述字,即epoll句柄。
2)op参数表示增加还是删除一个监控事件,它有三个选项:
- EPOLL_CTL_ADD:向epoll实例注册文件描述符对应的事件
- EPOLL_CTL_DEL:向epoll实例删除文件描述符对应的事件
- EPOLL_CTL_MOD:修改文件描述符对应的事件
3)fd参数表示注册事件的文件描述符
4)event参数表示注册的事件类型,在这个结构体里可以设置用户需要的数据,最常见的是使用联合结构里的 fd 字段,表示事件所对应的文件描述符。
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events epoll事件 */
epoll_data_t data; /* User data variable 用户数据 */
};
而epoll事件的类型和poll类型,都是基于 mask 的事件类型,事件类型有以下几种:
- EPOLLIN:表示对应的文件描述字可以读;
- EPOLLOUT:表示对应的文件描述字可以写;
- EPOLLRDHUP:表示套接字的一端已经关闭,或者半关闭;
- EPOLLHUP:表示对应的文件描述字被挂起;
- EPOLLET:设置为 edge-triggered,默认为 level-triggered。
返回值
成功返回0,失败返回-1,并设置errno。
epoll_wait
函数原型
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);
epoll_wait()函数和select、poll函数一样,调用者的进程被挂起,等待内核I/O事件的分发。
参数
1)epfd参数代表epoll实例描述字
2)events参数返回给用户空间需要处理的I/O事件,该参数是一个数组,数组的大小由epoll_wait的返回值决定,返回的数组中每个元素都是一个需要处理的I/O事件。(该数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件)
events表示具体的事件类型的取值和epoll_ctl中可设置的值一样。
由于epoll_wait只返回检测到的就绪事件,所以可以极大地提高应用程序索引就绪文件描述符地效率。(select和poll都需要遍历所有已经注册的文件描述符并找到其中的就绪者)
3)maxevents参数是一个大于0的整数,表示epoll_wait可以返回的最大值。
4)timeout参数是epoll_wait阻塞调用的超时值,如果这个值为-1,表示一直阻塞,直到有事件发生;如果设置为0,则立即返回,即使没有任何I/O事件发生。
返回值
成功返回一个大于0的数,表示就绪的文件描述符个数;返回0,表示超时时间到;出错则返回-1。
LT(Level Trigger)和ET模式(Edge Trigger)
epoll对文件描述符的操作有两种模式:LT(条件触发)和ET(边缘触发)。LT是默认工作模式,此模式下,epoll相当于一个效率较高的poll。为了使用ET模式,应该往epoll内核事件表中注册一个文件描述符上的EPOLLET事件。
对于LT模式,当epoll_wait检测到其上有事件发生并将事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。
而对于ET模式,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。
所以,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。
注意:使用ET模式的文件描述符应该是非阻塞的。如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态。
实验
对select和poll分别进行实验,相关程序中,当接收到对端发来的数据后,只打印一条信息,但是并不从缓冲区中读出数据,以此来判断 select 和 poll 是采用的LT模式还是ET模式。
对于select进行实验,可以发现,程序一直在循环打印信息。
对于poll进行实验,可以发现,程序同样一直在循环打印信息。
综上,select和poll都是条件触发的,即LT模式。