一、核心特点(对比 select、poll 优势)
- 存储结构:用红黑树存文件描述符集合,无数量上限,查找高效
- 数据拷贝:集合创建在内核层,避免应用层 - 内核层反复拷贝,降低开销
- 事件处理:返回到达事件,无需遍历,直接处理,效率高
- 触发模式:支持水平触发(低速)和边沿触发(高速),适配不同场景
二、操作流程与关键函数
- 创建集合:
int epoll_create(int size)
- 功能:通知内核创建文件描述符集合
- 参数:
size
填监测的文件描述符预期数量(实际无严格限制,传正整数即可 ) - 返回:成功返回集合的文件描述符(用于后续操作),失败返回
-1
- 管理集合(增删改):
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
- 功能:操作 epoll 集合(添加、修改、删除文件描述符及事件)
- 参数:
epfd
:epoll_create
返回的集合描述符op
:操作类型,如EPOLL_CTL_ADD
(添加)、EPOLL_CTL_MOD
(修改)、EPOLL_CTL_DEL
(删除)fd
:要操作的文件描述符event
:事件结构体,含events
(事件类型,如EPOLLIN
读、EPOLLOUT
写 )和data
(关联数据,常用data.fd
存关注的文件描述符 )
- 返回:成功返回
0
,失败返回-1
- 监测事件:
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
- 功能:通知内核开始监测事件,阻塞等待事件触发
- 参数:
epfd
:要监测的集合描述符events
:数组,用于存返回的到达事件结果maxevents
:最多处理的事件个数(需 ≤events
数组大小 )timeout
:超时时间,-1
表示一直阻塞,0
立即返回,正数为毫秒级超时
- 返回:成功返回触发的事件数量,失败返回
-1
- 事件类型与数据结构
epoll_data_t
联合结构体:可存指针、文件描述符等,常用fd
关联关注的描述符struct epoll_event
:含events
(事件类型标记,如EPOLLIN
/EPOLLOUT
)和data
(epoll_data_t
类型数据 )
三、核心原理与数据结构
epoll
在内核中维护两个关键数据结构,共同实现高效事件管理:
红黑树(RB-Tree)
- 用途:存储所有被监控的 FD 及其关联的事件(如
EPOLLIN
、EPOLLOUT
)。 - 优势:插入、删除、查找 FD 的时间复杂度为 O(log n),支持动态增删大量 FD,无数量上限(仅受系统内存限制)。
- 用途:存储所有被监控的 FD 及其关联的事件(如
就绪链表(Ready List)
- 用途:缓存所有触发了事件的 FD(就绪 FD)。
- 优势:
epoll_wait
直接从链表中获取就绪 FD,无需遍历全部 FD,效率极高。
四、关键函数详解
1. 创建 epoll 实例:epoll_create
#include <sys/epoll.h>
int epoll_create(int size);
功能:向内核申请创建一个
epoll
实例(管理 FD 集合的句柄)。参数:
size
是历史遗留参数,早期用于提示内核预分配空间,现代 Linux 中已忽略(只需传一个正整数即可)。返回值:成功返回
epoll
实例的文件描述符(epfd
),失败返回-1
(需检查errno
)。注意:
epfd
本身也是一个 FD,使用完毕后需用close(epfd)
释放资源。
2. 管理监控的 FD:epoll_ctl
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:向
epoll
实例添加、修改或删除被监控的 FD 及事件。参数:
epfd
:epoll_create
返回的实例句柄。op
:操作类型:EPOLL_CTL_ADD
:添加 FD 到监控集合。EPOLL_CTL_MOD
:修改已监控 FD 的事件类型。EPOLL_CTL_DEL
:从监控集合中删除 FD(此时event
可设为NULL
)。
fd
:需要监控的文件描述符(如 socket FD)。event
:事件结构体,定义如下:struct epoll_event { uint32_t events; // 事件类型(位掩码) epoll_data_t data; // 关联数据(用户自定义) }; typedef union epoll_data { void *ptr; // 指针(可关联自定义数据结构) int fd; // 常用:关联被监控的 FD 本身 uint32_t u32; uint64_t u64; } epoll_data_t;
events
常用类型:EPOLLIN
:FD 可读(如 socket 收到数据)。EPOLLOUT
:FD 可写(如 socket 发送缓冲区空闲)。EPOLLERR
:FD 发生错误(无需主动设置,内核自动触发)。EPOLLET
:启用边沿触发模式(默认是水平触发)。EPOLLONESHOT
:事件触发一次后自动取消监控(需重新添加才能再次监控)。
返回值:成功返回
0
,失败返回-1
(如 FD 已被监控、权限不足等)。
3. 等待就绪事件:epoll_wait
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
功能:阻塞等待监控集合中就绪的事件,将结果存入
events
数组。参数:
epfd
:epoll
实例句柄。events
:用户分配的数组,用于接收就绪事件(输出参数)。maxevents
:events
数组的最大长度(必须 ≥ 1)。timeout
:超时时间(毫秒):-1
:永久阻塞,直到有事件就绪。0
:立即返回(非阻塞模式)。>0
:最多阻塞timeout
毫秒,超时后返回0
。
返回值:
- 成功:返回就绪事件的数量(
0
表示超时,无就绪事件)。 - 失败:返回
-1
(如被信号中断,需检查errno
)。
- 成功:返回就绪事件的数量(
五、与 select/poll 的对比
特性 | select | poll | epoll |
---|---|---|---|
监控 FD 数量上限 | 有(通常 1024) | 无(受系统内存限制) | 无(受系统内存限制) |
事件存储 | 位图(固定大小) | 数组(动态分配) | 红黑树 + 就绪链表 |
事件检测方式 | 遍历所有 FD | 遍历所有 FD | 直接取就绪链表(无需遍历) |
数据拷贝 | 每次调用拷贝全部 FD | 每次调用拷贝全部 FD | 仅初始化时拷贝一次 |
触发模式 | 仅水平触发 | 仅水平触发 | 水平触发 + 边沿触发 |
时间复杂度 | O(n) | O(n) | O(1)(获取就绪事件) |
高并发表现 | 差(FD 越多越慢) | 较差(遍历开销大) | 优秀(无遍历开销) |
六、典型使用流程
- 创建 socket 并绑定端口,设为监听状态(
listen
)。 - 调用
epoll_create
创建epoll
实例(epfd
)。 - 调用
epoll_ctl
添加监听 socket 到epoll
集合(关注EPOLLIN
事件)。 - 循环调用
epoll_wait
等待就绪事件:- 若监听 socket 就绪(新连接),调用
accept
获取客户端 socket,添加到epoll
集合。 - 若客户端 socket 就绪(可读/可写),处理数据(读/写操作)。
- 若监听 socket 就绪(新连接),调用
- 断开连接时,调用
epoll_ctl
从集合中删除客户端 socket。