一、为什么需要 epoll
阻塞 socket 服务端一次只能等待一个连接或一个读写事件。客户端数量少时问题不大,但连接数变多后,多进程和多线程模型会带来上下文切换、内存占用和调度开销。
Linux 下高并发网络服务常用 epoll。
epoll 适合:
- 大量连接。
- 多数连接空闲。
- 事件驱动服务端。
- 长连接服务。
二、IO 多路复用
IO 多路复用的核心思想:
1 | 一个线程同时监听多个 fd,哪个 fd 就绪就处理哪个 |
常见接口:
selectpollepoll
epoll 是 Linux 上常用的高性能方案。
三、epoll 基本流程
1 | epoll_create1 -> epoll_ctl ADD -> epoll_wait -> handle events |
含义:
epoll_create1:创建 epoll 实例。epoll_ctl:添加、修改、删除监听 fd。epoll_wait:等待事件发生。- handle events:处理读、写、关闭等事件。
四、事件类型
常见事件:
1 | EPOLLIN 可读 |
入门阶段建议先使用默认的水平触发,不要一开始就用 EPOLLET。
五、水平触发和边缘触发
水平触发 LT:
- 只要 fd 还有数据可读,
epoll_wait就会继续通知。 - 编程简单。
- 入门推荐。
边缘触发 ET:
- 状态变化时通知一次。
- 必须配合非阻塞 IO。
- 必须循环读到
EAGAIN。 - 性能好,但更容易写错。
六、Reactor 模型
Reactor 是一种事件驱动模型:
1 | 事件监听器 -> 事件分发 -> 事件处理器 |
一个简单 Reactor 服务端包含:
- acceptor:处理新连接。
- event loop:等待事件。
- connection:保存连接状态。
- handler:处理读写逻辑。
伪代码:
1 | while running: |
七、非阻塞 IO
epoll 通常和非阻塞 fd 一起使用。
设置非阻塞:
1 |
|
非阻塞读写可能返回:
1 | EAGAIN |
这不是错误,而是表示当前没有数据或暂时不能写。
八、连接状态管理
真实服务不能只保存 fd,还要保存连接状态。
常见字段:
1 | struct connection { |
为什么需要写缓冲?
因为 write 不保证一次写完全部数据。网络繁忙时只能写一部分,剩下的数据要等下次 EPOLLOUT。
九、常见坑
9.1 忘记处理半包和粘包
TCP 是字节流,不保留消息边界。应用层必须设计协议,例如:
- 固定长度头。
- 分隔符。
- length + body。
9.2 阻塞操作放进事件循环
事件循环里不要做耗时操作,例如:
- 慢磁盘 IO。
- 复杂计算。
- 阻塞数据库请求。
- 长时间 sleep。
否则一个连接会拖慢所有连接。
9.3 没处理对端关闭
read 返回 0 表示对端关闭连接,需要清理 fd。
9.4 边缘触发没有读到 EAGAIN
ET 模式必须循环读,否则可能再也收不到通知。
十、学习路线
建议:
- 写阻塞 TCP server。
- 写多线程 TCP server。
- 写 select server。
- 写 epoll LT server。
- 改成非阻塞。
- 实现 length + body 协议。
- 加写缓冲。
- 再研究 ET 和线程池。
十一、总结
epoll 不是网络编程的起点,而是进阶工具。先理解 socket、阻塞 IO 和 TCP 字节流,再学习 epoll,会更容易理解它解决了什么问题。
Reactor 的核心是事件循环和状态管理。写对一个稳定的 epoll 服务端,重点不只是 API,而是连接生命周期、缓冲区和错误处理。
- 本文链接: https://blog.hansong.icu/2026/06/21/Linux_Dev_Epoll_Reactor/
- 版权声明: 本博客所有文章除特别声明外,均默认采用 CC BY-NC-SA 4.0 许可协议。