epoll
是Linux内核为处理大批量文件描述符而做了改进的poll
,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
epoll的优点
支持一个进程打开大数目的文件描述符:
select
在一个进程所打开的文件描述符是有一定限制的,由FD_SETSIZE
设置,默认值是1024。不过epoll
则没有这个限制,它所支持的文件描述符的上限是系统最大可以打开文件的数目,可以在/proc/sys/fs/file-max
查看,一般来说,这个数目和系统的内存有很大关系。I/O效率不随文件描述符数目增加而线性下降: 传统的
select/poll
有一个缺点就是在一个很大的socket
集合时,不过由于网络延时,任一时间只有部分的socket
是 “活跃” 的,但是select/poll
每次调用都会线性扫描全部的集合,导致效率呈现下降。但是epoll
不存在这个问题,它只会对 “活跃” 的socket
进行操作。如果对于所有的socket
基本上都是 “活跃” 的,epoll
并不比select/poll
效率高。如果应用中存在大量的短连接,epoll_ctl
将被频繁地调用,这也可能会影响epoll
的效率。
epoll的系统调用
epoll_create函数
1 |
|
创建一个epoll
的文件描述符,size
用来告诉内核这个监听的数目一共有多大。
epoll_ctl函数
1 |
|
epoll
的事件注册函数,它不同与select
是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create
的返回值,第二个参数表示动作,用三个宏来表示:
- EPOLL_CTL_ADD : 注册新的
fd
到epfd
- EPOLL_CTL_MOD : 修改已经注册的
fd
的监听事件 - EPOLL_CTL_DEL : 从
epfd中
删除一个fd
第三个参数是需要监听的fd
,第四个参数是告诉内核需要监听什么事,struct epoll_event
结构如下:
1 | typedef union epoll_data { |
events
可以是以下几个宏的集合:
- EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
- EPOLLOUT :表示对应的文件描述符可以写
- EPOLLPRI :表示对应的文件描述符有紧急的数据可读
- EPOLLERR :表示对应的文件描述符发生错误
- EPOLLHUP :表示对应的文件描述符被挂断
- EPOLLET : 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
- EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个
socket
的话,需要再次把这个socket
加入到EPOLL队列里
epoll_wait函数
1 |
|
等待事件的产生,类似于select/poll
调用。参数events
用来从内核得到事件的集合,maxevents
告之内核这个events
有多大,这个maxevents
的值不能大于创建epoll_create
时的size
,参数timeout
是超时时间(0会立即返回,-1是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。
epoll工作模式
epoll对文件描述符的操作有两种模式:LT(level trigger)和ET(edge trigger)。LT模式是默认模式,LT模式与ET模式的区别如下:
LT模式 : 是缺省的工作方式,并且同时支持block
和non-block
socket。在这种模式下,当epoll_wait
检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait
时,会再次响应程序并通知此事件。
ET模式 : 是高速工作方式,只支持non-block socket
。在这种模式下,当epoll_wait
检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait
时,不会再次响应应用程序并通知此事件。
使用epoll的echo服务器程序
LT模式
代码如下:
1 |
|
- 创建一个监听套接字。将监听套接字添加到
epoll
事件中,events
使用的是EPOLLIN
。 - 调用
epoll_wait
以等待新的连接或者现有连接上有数据可读。如果返回的fd
是监听套接字,表示有新的连接,则将新的描述符添加到epoll
事件中。 - 当
epoll_wait
返回的是现有连接上有数据可读,则从这个连接上读取数据。如果客户端终止或收到 RST 则从epoll
事件删除该描述符,并关闭它。
ET模式
代码如下:
1 | static int Fcntl(int fd, int cmd, int arg) { |
在ET模式下,描述符只能是非阻塞的。对于非阻塞socket,read/write返回-1不一定网络真的出错了。
在epoll
的ET模式下,正确的读写方式为:
读:只要可读,就一直读,直到返回0,或者 errno = EAGAIN 或者 EWOULDBLOCK
写:只要可写,就一直写,直到数据发送完,或者 errno = EAGAIN 或者 EWOULDBLOCK