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; std::map<int, Callback> handlers; 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 是「只负责通知有事、不负责处理事」的分发器。
|