按照man手册的说法: 是为处理大批量句柄而作了改进的poll. 它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel 2.5.44) 它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll 有3个相关的系统调用
创建一个epoll的句柄
接口:
#include
int epoll_create(int size);
一旦成功,这些系统调用将返回一个非负的文件描述符。出现错误时,返回-1,并将errno设置为指示错误。
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
成功后,epoll_ctl()返回零。当发生错误时,epoll_ctl()返回-1,并适当设置errno。
epoll的事件注册函数
第二个参数的取值:
取值 | 含义 |
---|---|
EPOLL_CTL_ADD | 注册新的fd到epfd中 |
EPOLL_CTL_MOD | 修改已经注册的fd的监听事件 |
EPOLL_CTL_DEL | 从epfd中删除一个fd; |
struct epoll_event结构如下:
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_data_t data; /* User data variable */
} __EPOLL_PACKED;
events可以是以下几个宏的集合:
宏 | 含义 |
---|---|
EPOLLIN | 表示对应的文件描述符可以读 (包括对端SOCKET正常关闭); |
EPOLLOUT | 表示对应的文件描述符可以写; |
EPOLLPRI | 表示对应的文件描述符有紧急的数据可读 (这里应该表示有带外数据到来); |
EPOLLERR | 表示对应的文件描述符发生错误; |
EPOLLHUP | 表示对应的文件描述符被挂断; |
EPOLLET | 将EPOLL设为边缘触发(Edge Triggered)模式, 这是相对于水平触发(Level Triggered)来说的. |
EPOLLONESHOT | 只监听一次事件, 当监听完这次事件之后, 如果还需要继续监听这个socket的话, 需要再次把这个socket加入到EPOLL队列里 |
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
收集在epoll监控的事件中已经发送的事件
struct eventpoll{
....
/*红黑树的根节点,这颗树中存储着所有添加到epoll中的需要监控的事件*/
struct rb_root rbr;
/*双链表中则存放着将要通过epoll_wait返回给用户的满足条件的事件*/
struct list_head rdlist;
....
};
struct epitem{
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
}
epoll有2种工作方式-水平触发(LT)和边缘触发(ET)
如果我们在第1步将socket添加到epoll描述符的时候使用了EPOLLET标志, epoll进入ET工作模式。
对于多连接, 且多连接中只有一部分连接比较活跃时, 比较适合使用epoll。
例如, 典型的一个需要处理上万个客户端的服务器, 例如各种互联网APP的入口服务器, 这样的服务器就很适合epoll.如果只是系统内部, 服务器和服务器之间进行通信, 只有少数的几个连接, 这种情况下用epoll就并不合适. 具体要根据需求和场景特点来决定使用哪种IO模型。
代码链接:
https://gitee.com/ding-xushengyun/linux__cpp/tree/master/4_epoll
基于 LT 版本稍加修改即可
注意: 此代码考虑 listen_sock ET 的情况. 如果将 listen_sock 设为 ET, 则需要非阻塞轮询的方式 accept. 否则会导致同一时刻大量的客户端同时连接的时候, 只能 accept 一次的问题。
对于读取操作:
(1) 当buffer由不可读状态变为可读的时候,即由空变为不空的时候。
(2) 当有新数据到达时,即buffer中的待读内容变多的时候。
(3) 当buffer中有数据可读(即buffer不空)且用户对相应fd进行epoll_mod修改为epol_IN事件时。
对于写操作:
(1) 当buffer由不可写变为可写的时候,即由满状态变为不满状态的时候。
(2) 当有旧数据被发送走时,即buffer中待写的内容变少得时候。
(3) 当buffer中有可写空间(即buffer不满)且用户对相应fd进行epoll_mod修改为epol_OUT事件时
代码链接
https://gitee.com/ding-xushengyun/linux__cpp/tree/master/Reactor