banner
NEWS LETTER

Linux 网络编程进阶:epoll 与 Reactor 模型

Scroll down

一、为什么需要 epoll

阻塞 socket 服务端一次只能等待一个连接或一个读写事件。客户端数量少时问题不大,但连接数变多后,多进程和多线程模型会带来上下文切换、内存占用和调度开销。

Linux 下高并发网络服务常用 epoll

epoll 适合:

  • 大量连接。
  • 多数连接空闲。
  • 事件驱动服务端。
  • 长连接服务。

二、IO 多路复用

IO 多路复用的核心思想:

1
一个线程同时监听多个 fd,哪个 fd 就绪就处理哪个

常见接口:

  • select
  • poll
  • epoll

epoll 是 Linux 上常用的高性能方案。

三、epoll 基本流程

1
epoll_create1 -> epoll_ctl ADD -> epoll_wait -> handle events

含义:

  • epoll_create1:创建 epoll 实例。
  • epoll_ctl:添加、修改、删除监听 fd。
  • epoll_wait:等待事件发生。
  • handle events:处理读、写、关闭等事件。

四、事件类型

常见事件:

1
2
3
4
5
EPOLLIN   可读
EPOLLOUT 可写
EPOLLERR 错误
EPOLLHUP 对端关闭或挂起
EPOLLET 边缘触发

入门阶段建议先使用默认的水平触发,不要一开始就用 EPOLLET

五、水平触发和边缘触发

水平触发 LT:

  • 只要 fd 还有数据可读,epoll_wait 就会继续通知。
  • 编程简单。
  • 入门推荐。

边缘触发 ET:

  • 状态变化时通知一次。
  • 必须配合非阻塞 IO。
  • 必须循环读到 EAGAIN
  • 性能好,但更容易写错。

六、Reactor 模型

Reactor 是一种事件驱动模型:

1
事件监听器 -> 事件分发 -> 事件处理器

一个简单 Reactor 服务端包含:

  • acceptor:处理新连接。
  • event loop:等待事件。
  • connection:保存连接状态。
  • handler:处理读写逻辑。

伪代码:

1
2
3
4
5
6
7
8
9
while running:
events = epoll_wait()
for event in events:
if event.fd == listen_fd:
accept new connection
else if event readable:
read data and handle request
else if event writable:
send pending data

七、非阻塞 IO

epoll 通常和非阻塞 fd 一起使用。

设置非阻塞:

1
2
3
4
#include <fcntl.h>

int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);

非阻塞读写可能返回:

1
2
EAGAIN
EWOULDBLOCK

这不是错误,而是表示当前没有数据或暂时不能写。

八、连接状态管理

真实服务不能只保存 fd,还要保存连接状态。

常见字段:

1
2
3
4
5
6
7
struct connection {
int fd;
char read_buf[4096];
char write_buf[4096];
size_t write_len;
size_t write_pos;
};

为什么需要写缓冲?

因为 write 不保证一次写完全部数据。网络繁忙时只能写一部分,剩下的数据要等下次 EPOLLOUT

九、常见坑

9.1 忘记处理半包和粘包

TCP 是字节流,不保留消息边界。应用层必须设计协议,例如:

  • 固定长度头。
  • 分隔符。
  • length + body。

9.2 阻塞操作放进事件循环

事件循环里不要做耗时操作,例如:

  • 慢磁盘 IO。
  • 复杂计算。
  • 阻塞数据库请求。
  • 长时间 sleep。

否则一个连接会拖慢所有连接。

9.3 没处理对端关闭

read 返回 0 表示对端关闭连接,需要清理 fd。

9.4 边缘触发没有读到 EAGAIN

ET 模式必须循环读,否则可能再也收不到通知。

十、学习路线

建议:

  1. 写阻塞 TCP server。
  2. 写多线程 TCP server。
  3. 写 select server。
  4. 写 epoll LT server。
  5. 改成非阻塞。
  6. 实现 length + body 协议。
  7. 加写缓冲。
  8. 再研究 ET 和线程池。

十一、总结

epoll 不是网络编程的起点,而是进阶工具。先理解 socket、阻塞 IO 和 TCP 字节流,再学习 epoll,会更容易理解它解决了什么问题。

Reactor 的核心是事件循环和状态管理。写对一个稳定的 epoll 服务端,重点不只是 API,而是连接生命周期、缓冲区和错误处理。

其他文章
目录导航 置顶
  1. 1. 一、为什么需要 epoll
  2. 2. 二、IO 多路复用
  3. 3. 三、epoll 基本流程
  4. 4. 四、事件类型
  5. 5. 五、水平触发和边缘触发
  6. 6. 六、Reactor 模型
  7. 7. 七、非阻塞 IO
  8. 8. 八、连接状态管理
  9. 9. 九、常见坑
    1. 9.1. 9.1 忘记处理半包和粘包
    2. 9.2. 9.2 阻塞操作放进事件循环
    3. 9.3. 9.3 没处理对端关闭
    4. 9.4. 9.4 边缘触发没有读到 EAGAIN
  10. 10. 十、学习路线
  11. 11. 十一、总结
请输入关键词进行搜索