EventLoop——事件循环有什么

EventLoop 是网络系统的心脏。它回答的问题只有一个:「什么时候有事发生?」 它是网络系统里第一个被逼出来的组件,也是唯一拥有自己线程的组件。这一篇讲它有什么——职责、内部结构、边界、异常、形态。


一、它解决什么问题

早期代码靠遍历所有连接来发现「谁有数据」:

1
2
3
for (int i = 0; i < count; i++) {
if (有数据可读(clients[i])) { ... }
}

100 个连接里可能只有 2 个有数据,却要逐个检查。遍历太慢——这就是 EventLoop 被逼出来的原因。

解法:让操作系统直接告诉「谁有事」,不用自己查。

1
int n = epoll_wait(epfd, events, MAX_EVENTS, -1);  // 只返回有事的

EventLoop 的本质:把「遍历所有连接」变成「只处理有事的连接」。


二、它有什么(内部结构)

EventLoop 是「数据 + 算法 + 接口」的完整组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class EventLoop {
// —— 数据 ——
int epfd; // epoll 实例(fd)
std::map<int, Callback> handlers; // fd → 回调 的映射
TimerQueue timers; // 定时器队列

public:
// —— 接口 ——
EventLoop();
void run(); // 启动循环(阻塞)
void stop(); // 停止循环

void on_accept(int fd, Callback cb); // 注册「新连接」事件
void on_readable(int fd, Callback cb); // 注册「可读」事件
void on_writable(int fd, Callback cb); // 注册「可写」事件
void add_timer(ms, Callback cb); // 注册定时器
};
1
2
3
数据:epoll 实例 + fd→回调映射 + 定时器队列
算法:epoll_wait 等待 → 遍历就绪事件 → 分发到对应回调
接口:run / stop / on_xxx(注册事件)/ add_timer

核心循环

1
2
3
4
5
6
epoll_wait 阻塞等待事件
→ 有新连接事件 → 调 on_accept 注册的回调
→ 有可读事件 → 调 on_readable 注册的回调
→ 有可写事件 → 调 on_writable 注册的回调
→ 定时器到期 → 调 add_timer 注册的回调
→ 回到 epoll_wait 继续等

三、它的边界:只管「什么时候」,不管「是什么」

EventLoop 的职责非常窄,也很关键:

1
2
✅ 管:什么时候有事(等待、分发、定时)
❌ 不管:事情是什么(连接怎么建立、字节怎么处理、消息怎么解析)
1
2
3
EventLoop 看到「fd 3 可读了」
→ 它只做一件事:调用「fd 3 可读」的回调
→ 至于这个回调里是收数据、还是关连接,EventLoop 一概不知

EventLoop 是「分发器」,不是「处理者」。 它把「什么时候」和「是什么」彻底分开——这正是它能成为心脏、其他组件围绕它运转的原因。


四、它的形态:有状态组件,类封装

EventLoop 是有状态组件:

1
2
3
有状态:epoll 实例、回调映射、定时器队列都是它的状态
有生命周期:构造创建,run 启动,stop 停止
有控制流:自己在独立线程里循环,自己决定什么时候处理什么

所以它用类封装(不是函数导出):

1
2
3
4
EventLoop loop;
loop.on_accept(server, on_new_conn);
loop.on_readable(conn, on_data);
loop.run(); // 阻塞,自己跑自己的循环

调用者只负责「创建、注册回调、启动、停止」,中间的事件循环是 EventLoop 自己在跑——这就是它有控制流的体现。


五、它的异常

1
2
3
4
5
6
7
8
9
10
11
EINTR(信号中断):
epoll_wait 被信号打断,返回 -1
→ 处理:判断 errno == EINTR,重试(continue)

EMFILE(fd 耗尽):
accept 时文件描述符用完了
→ 处理:记录警告,可能暂时拒绝新连接

定时器精度不够:
定时器到期但延迟处理
→ 处理:接受精度误差,或用更高精度定时源

EventLoop 的异常都是「运行环境」层面的,不是「业务」层面的。 它不该处理连接断开(那是 Connection 的事)、不该处理消息格式错误(那是解析层的事)。


六、它和其他组件的关系

1
2
3
4
5
6
7
8
9
10
11
EventLoop 是心脏,其他组件被它驱动:

EventLoop
├── 驱动 Connection(新连接事件 → 创建/销毁连接)
├── 驱动 Session(连接建立 → 创建会话;断开 → 销毁会话)
├── 引用 Buffer(可读 → 写缓冲;可写 → 从缓冲发)
└── 引用 Protocol(消息格式定义)

方向是单向的:
EventLoop 知道回调的存在
但回调里的 Connection/Session/Buffer 不反向依赖 EventLoop 的内部
1
2
EventLoop  ← 驱动 →  Connection / Session
EventLoop ← 引用 → Buffer / Protocol

七、小结

1
2
3
4
5
6
7
8
9
EventLoop 有什么:
职责:什么时候有事(等待、分发、定时)
结构:epoll 实例 + fd→回调映射 + 定时器队列
接口:run / stop / on_accept / on_readable / on_writable / add_timer
边界:只管「什么时候」,不管「是什么」
形态:有状态组件,类封装,独立线程
异常:EINTR / EMFILE / 定时器精度

一句话:EventLoop 是「只负责通知有事、不负责处理事」的分发器。