Epoll IO 模型是 Linux 中用于 I/O 多路复用的机制,可以用于监听多个文件描述符上的事件,以及非阻塞地等待这些事件的发生。其工作机制大致如下:
- 首先,初始化一个 Epoll 实例,这个实例主要在内核中维护的两个数据结构,一个红黑树,用于存储被检测的文件描述符,一个链表,用于存储就绪事件
- 然后,我们将要监控的文件描述符放到 Epoll 实例的红黑树中,然后内核开始遍历红黑树检测文件描述符是否有注册的事件发生
- 当有事件发生时,内核会将这个事件添加到链表中,等待用户处理
- 用户执行操作后,就绪事件就会从内核的链表中拷贝到用户空间中,用户就可以取出事件进行处理
在这个过程中,我们发现内核使用了红黑树来维护和管理待监控的文件描述符集合,红黑树并没有文件描述符上限的要求,所以,只要内存足够,我们可以添加更多的文件描述符到 Epoll 的内核红黑树中。这也有利于提高 epoll 的并发量。
但是,需要注意的是,在 Linux 系统中,默认情况下,每个进程可以打开的文件描述符数量是有限制的。如果超出了文件描述符的限制,就会出现无法打开新的文件描述符的情况,影响程序的正常运行。进程可以配置最大的文件描述符数量。
另外,与 Select IO 模型相比,每次的 Select 调用都需要进行两次内核和用户空间的数据交换。而 Epoll 则是在通知用户事件时,拷贝一次。这非常有利于提高 epoll 的性能。
用户处理事件时,不需要遍历所有的文件描述符,因为 epoll 只会返回已就绪的文件描述符,这也有利于提高 epoll 的性能。
下面是 Epoll 操作相关函数:
// 创建 epoll 实例 int epoll_create1 (int __flags)
__flags
:如果设置为EPOLL_CLOEXEC
表示程序在退出之前关闭文件描述符
函数如果失败,则返回 -1,可通过 errno 来查看具体错误信息
int epoll_ctl (int __epfd, int __op, int __fd, struct epoll_event *__event)
__epfd
:epoll 实例的文件描述符。__op
:要执行的操作,可以是以下值之一:EPOLL_CTL_ADD
:向 epoll 实例注册一个新的文件描述符。EPOLL_CTL_MOD
:修改已注册文件描述符的事件。EPOLL_CTL_DEL
:从 epoll 实例中删除一个文件描述符。
__fd
:要注册、修改或删除的文件描述符。__event
:指向epoll_event
结构体的指针,用于描述要注册、修改或删除的事件。可以为 NULL(仅在执行EPOLL_CTL_DEL
操作时)
函数成功返回 0,否则返回 -1,可通过 errno 来指示错误。
int epoll_wait (int __epfd, struct epoll_event *__events, int __maxevents, int __timeout)
__epfd
:epoll 实例的文件描述符。__events
:指向epoll_event
结构体数组的指针,用于存储就绪的文件描述符和事件。__maxevents
:events
数组中能存储的最大事件数。__timeout
:等待就绪事件的超时时间(以毫秒为单位)。可以为以下值之一:-1
:无限等待。0
:立即返回。- 大于 0 的整数:等待指定毫秒数后返回。
函数执行成功,则返回就绪的文件描述符数量,否则返回 -1,可通过 errno 指示错误。
示例代码:
#include <cstdio> #include <sys/epoll.h> #include <set> #define MAX_EVENT_NUM 128 void test() { // 初始化 Epoll 对象 int epoll_fd = epoll_create1(EPOLL_CLOEXEC); if (-1 == epoll_fd) { perror("epoll_create1 error"); return; } // 已连接的文件描述符 std::set<int> fds = { 100, 101, 102, 103, 104 }; int ret = -1; // 注册文件描述符 for (auto fd : fds) { // 设置文件描述符绑定的读写事件 struct epoll_event fd_event; fd_event.events = EPOLLIN; // 写事件 fd_event.data.fd = fd; // 将文件描述符和事件存储到红黑树中 ret = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &fd_event); if (-1 == ret) { perror("epoll_ctl error"); continue; } } // 从 Epoll 中删除某个文件描述符 int del_fd = 101; ret = epoll_ctl(epoll_fd, EPOLL_CTL_DEL, del_fd, nullptr); if (-1 == ret) { perror("epoll_ctl error"); return; } // 从 fds 中也要删除文件描述符 fds.erase(del_fd); // 开始监控文件描述符 struct epoll_event events[MAX_EVENT_NUM]; ret = epoll_wait(epoll_fd, events, MAX_EVENT_NUM, -1); if (-1 == ret) { perror("epoll_wait error"); return; } } int main() { test(); return 0; }