epoll是linux高并发网络服务的必要选择,核心流程为epoll_create1、epoll_ctl注册事件、epoll_wait等待就绪;必须配合非阻塞socket使用et模式,并正确管理fd生命周期与上下文绑定。

epoll 是 Linux 下高性能网络服务的事实标准,不是“可选方案”,而是高并发场景下的必要选择。它不解决单次 I/O 速度问题,而是让程序能用极少的线程,精准、低开销地感知成千上万个 socket 的状态变化。
epoll 的核心使用流程不能跳过这三步
所有 epoll 应用都围绕三个系统调用展开,缺一不可:
-
epoll_create1(0):创建一个 epoll 实例,返回一个内核事件表的文件描述符(epfd)。推荐用
epoll_create1(0)替代旧的epoll_create(size),size 参数已废弃,仅作兼容保留。 -
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev):向事件表注册 socket fd 及关心的事件(如
EPOLLIN | EPOLLET)。每个 fd 只需注册一次,后续修改用EPOLL_CTL_MOD,关闭连接前用EPOLL_CTL_DEL显式移除。 - epoll_wait(epfd, events, maxevents, timeout):阻塞等待就绪事件。它只返回真正有事件发生的 fd 列表,数量远小于总监听数,避免无效遍历。
事件模式选对才不丢数据:LT 与 ET 的实际取舍
默认是水平触发(LT),但生产环境普遍启用边缘触发(ET),前提是必须配合非阻塞 socket 使用:
Linux系统管理专家,覆盖12大模块:用户权限、SSH、存储、网络、systemd、防火墙、日志监控、备份恢复、TLS证书、Ansible、容器、IaC。提供配置、验证、加固、监控、备份、自动化、故障排查、回滚闭环。关键词:useradd、sudo、sshd_config、chmod、SEL...
-
LT 模式:只要 fd 处于就绪状态(比如接收缓冲区还有数据),每次
epoll_wait都会返回该 fd。适合新手,容错性强,但可能引发重复通知。 -
ET 模式:仅在状态变化时通知一次(例如从无数据变为有数据)。必须循环读/写直到
recv()或send()返回EAGAIN/EWOULDBLOCK,否则容易漏数据。性能更高,是 Nginx、Redis 等项目的标配。
fd 管理和内存安全的关键细节
epoll 本身不管理 socket 生命周期,这部分完全由应用负责:
- 每个注册进 epoll 的 fd,必须确保在其整个生命周期内有效;
close(fd)前务必先epoll_ctl(..., EPOLL_CTL_DEL, ...),否则可能触发内核警告或未定义行为。 -
epoll_event.data.ptr是唯一可靠的上下文绑定方式。建议把 socket 对应的连接结构体指针存入其中,这样在epoll_wait返回后,能直接通过events[i].data.ptr找到业务逻辑对象,无需哈希查找或数组索引。 - 不要在回调中直接
free()连接结构体——若同时触发读写事件,可能造成二次释放。应标记为待销毁,在事件循环空闲时统一清理。
常见误用导致性能断崖的几个坑
很多“用了 epoll 却没提速”的问题,根源不在 API,而在设计习惯:
- 把阻塞 socket 加入 epoll:ET 模式下必然卡死;LT 模式下虽能运行,但违背了事件驱动初衷,失去多路复用意义。
- 在
epoll_wait返回后,对每个就绪 fd 只调用一次read()就结束:LT 下可能残留数据,ET 下几乎必然丢数据。 - 频繁调用
epoll_ctl动态增删 fd:注册操作涉及内核红黑树调整,开销不小。应尽量在连接建立/断开时集中处理,而非在业务逻辑中反复开关事件。 - 忽略
EPOLLRDHUP:无法及时感知对端半关闭,导致连接长时间滞留。建议始终监听该事件,配合shutdown()做优雅断连。










