Linux / Network
epoll ET 模式要点
ET 模式的核心是“只在状态变化时提醒一次”,所以读写循环必须把缓冲区尽量处理干净。
LT 和 ET 的差异
LT 模式更像反复提醒:只要 fd 上还有数据,下次 epoll_wait 仍然可能返回。ET 模式只在状态从不可读变成可读时提醒一次,如果这次没有读完,后续可能不会再提醒。
读事件处理
ET 模式下 read/recv 通常要放进循环,直到返回 EAGAIN 或 EWOULDBLOCK。这个错误不是失败,而是在非阻塞 fd 上表示“当前已经没有更多数据可读”。
- fd 必须设置为非阻塞。
- 循环读到 EAGAIN 才能退出。
- 返回 0 表示对端关闭连接。
- 真实错误需要关闭 fd 或进入错误处理路径。
写事件处理
写事件同理,不能假设一次 send 就能发完响应。需要记录已经发送的偏移,剩余内容等下一次可写事件继续发。
while (sent < response.size()) {
ssize_t n = send(fd, response.data() + sent, response.size() - sent, 0);
if (n > 0) sent += n;
else if (errno == EAGAIN || errno == EWOULDBLOCK) break;
else close_connection(fd);
}
调试清单
- 连接卡住时先看 fd 是否非阻塞。
- CPU 占用异常时检查是否反复注册无意义写事件。
- 响应不完整时检查写偏移是否保存。
- 偶现丢请求时检查读循环是否提前退出。
事件循环中的易错点
ET 模式最怕“以为自己读完了”。只要一次事件没有把内核缓冲区读到 EAGAIN,后面就可能再也收不到提醒。写事件也一样,如果 send 只写出一部分,必须保存偏移量。
- 读事件循环到 EAGAIN 再退出。
- 写事件只在有待发送数据时注册。
- 连接关闭、读 0、真实错误要分开处理。
复习问题
复习时可以问自己三个问题:fd 是否非阻塞?读写循环是否处理 EAGAIN?半包和粘包由哪个缓冲区负责?如果这三个问题答得清楚,ET 模式基本就不会乱。