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;
}