本文参考社长的 TinyWebServer 庖丁解牛 epoll 常用APIepoll_create 函数 #include sys/epoll.hint epoll_create(int size); 创建一个指示 epoll 内核事件表的文件描述符,该描述符将用作其他 epoll 系统调用的
epoll 常用API epoll_create 函数本文参考社长的 TinyWebServer 庖丁解牛
#include <sys/epoll.h>
int epoll_create(int size);
创建一个指示 epoll 内核事件表的文件描述符,该描述符将用作其他 epoll 系统调用的第一个参数,此处的 size 参数不起作用。
epoll_ctl 函数#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
该函数用于操作内核事件表监控的文件描述符上的事件:注册、修改、删除:
epfd
:为 epoll_create 的句柄;op
:表示动作,用 3 个宏来表示:EPOLL_CTL_ADD
:注册新的 fd 到 epfd;EPOLL_CTL_MOD
:修改已经注册的 fd 的监听事件;EPOLL_CTL_DEL
:从 epfd 删除一个 fd;
event
:告诉内核需要监听的事件。
其中,event
是 epoll_event
结构体指针类型,表示内核监听的事件,具体定义如下:
struct epoll_event {
__uint32_t events;
epoll_data_t data;
};
events
描述事件类型,其中 epoll 事件类型有以下几种:EPOLLIN
:表示对应的文件描述符可读(包括对端SOCKET正常关闭)EPOLLOUT
:表示对应的文件描述符可写;EPOLLPRI
:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)EPOLLERR
:表示对应的文件描述符发生错误;EPOLLHUP
:表示对应的文件描述符被挂断;EPOLLLET
:将 EPOLL 设置为边缘触发(ET)模式;EPOLLONESHOT
:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个 socket 的话,需要再次把这个 socket 加入到 EPOLL 队列中。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
该函数用于等待所监控的文件描述符上有事件的产生,返回就绪的文件描述符的个数。
events
:用来存储内核得到的事件的集合;maxevents
:告知内核这个 events 有多大,这个值不能大于创建 epoll_create() 时的大小;timeout
:超时时间:-1
:阻塞;0
:立即返回,非阻塞;>0
:指定毫秒数;
- 返回值:成功返回有多少文件描述符就绪,时间到时返回 0,出错时返回-1。
-
调用函数
- select 和 poll 都是一个函数,epoll 是一组函数;
-
文件描述符数量
- select 使用线性表保存文件描述符的集合,文件描述符有上限,一般是 1024,但可以修改源码,重新编译内核,不推荐;
- poll 是使用链表存储文件描述符的集合,突破了文件描述符的上限;
- epoll 使用红黑树存储文件描述符的集合,突破了文件描述符的上限(通过命令 ulimit -n number 修改,仅对当前终端有效);
-
将文件描述符从用户传给内核:
- select 和 poll 将所有文件描述符拷贝到内核态,每次调用都需要拷贝;
- epoll 通过 epoll_create 建立一棵红黑树,通过 epoll_ctl 将要监听的文件描述符注册到红黑树上,文件描述符都在内核态;
-
内核判断就绪的文件描述符:
- select 和 poll 通过遍历文件描述符集合,判断哪个文件描述符上有事件发生;
- epoll_create 时,内核除了会建立一个红黑树来存储以后 epoll_ctl 传来的 fd 外,还会再建立一个 list 链表,用于存储准备就绪的事件。当 epoll_wait 调用时,仅仅观察这个 list 链表上有没有数据即可;
- epoll 是根据每个 fd 上面的回调函数(中断函数)判断,只有发生了时间的 socket 才会主动的去调用 callback 函数,其他空闲状态的 socket 则不会。若是就绪事件,则插入 list;
-
应用程序索引就绪文件描述符:
- select/epoll 只返回发生了事件的文件描述符的个数,若想要知道哪些文件描述符发生了事件,需要再次遍历;
- epoll 返回的是发生了事件的个数和结构体数组,结构体包含 socket 的信息,因此直接处理返回的数组即可;
-
工作模式:
- select/poll 都只能工作在低效的 LT 模式下;
- epoll 则可以工作在高效的 ET 模式,并且 epoll 还支持 EPOLLONESHOT 事件,可以进一步减少可读、可写和异常事件被触发的次数;
其实 ET 和 LT 哪个高效也是针对不同的任务而言。
-
应用场景:
- 如果所有的 fd 都是活跃连接,epoll 需要建立红黑树和链表,效率反而不高,不如 select/epoll;
- 如果监测的 fd 数目较小,且各个 fd 都比较活跃,建议使用 select/poll;
- 如果监测的 fd 数目非常大,并且单位时间内只有其中一小部分 fd 处于就绪状态,这个时候使用 epoll 能够明显提升性能。
-
LT 水平触发模式
- epoll_wait 检测到文件描述符有事件发生,则将其通知给应用程序,应用程序可以不立即处理该事件;
- 当下一次调用 epoll_wait 时,epoll_wait 还会再次向应用程序报告此事件,直至被处理。
Note:
一个事件只要有,就会一直触发。
socket 上只要有未读完的数据,就会一直产生 EPOLLIN 事件。所以读完数据要移除事件,避免一直触发。
-
ET 边缘触发模式
- epoll_wait 检测到文件描述符有事件发生,则将其通知给应用程序,应用程序必须立即处理该事件;
- 必须要一次性将数据读取完,使用非阻塞 I/O,读取到出现 eagain。
Note:
只有一个事件从无到有,才会触发。
socket 上每新来一次数据就会触发一次,如果某一次触发后,未将 socket 上的数据全部读完,也不会再次触发,除非再来一次数据。所以必须要一次性读完所有数据。如果未读完,需要再次将事件注册,
ET 模式必须配合非阻塞 I/O 实现,因为 ET 模式会一次性读取完所有的数据,如果是阻塞 I/O 的话,会导致线程阻塞,影响重新调用 epoll_wait 来监听其他事件。