et模式需显式设置epollet且必须与epollin/epollout组合使用,注册前socket须设为非阻塞,read/write须循环至eagain/ewouldblock。

epoll_create 和 epoll_ctl 要配对用 EPOLLET
ET 模式不是靠 epoll_wait 自动开启的,必须在注册 fd 时显式传入 EPOLLET 标志。漏掉这个,哪怕后面逻辑全按 ET 写,实际仍是 LT 行为,容易漏事件。
常见错误:只在 epoll_ctl 的 events 字段加 EPOLLET,却忘了同时设置 EPOLLIN 或 EPOLLOUT —— 这会导致 fd 注册失败(errno = EINVAL)。
正确写法示例:
struct epoll_event ev; ev.events = EPOLLIN | EPOLLET; // 必须显式或运算 ev.data.fd = sockfd; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);
- 不能只写
ev.events = EPOLLET,否则没监听任何事件 -
epoll_create1(0)比epoll_create更推荐,语义更清晰 - 注册前确保 socket 已设为非阻塞(
fcntl(fd, F_SETFL, O_NONBLOCK)),ET 下阻塞读/写会卡死
read/write 必须循环直到 EAGAIN/EWOULDBLOCK
ET 模式下,内核只通知一次“有数据可读”或“可写了”,但用户空间不保证一次性把缓冲区掏空。如果只调用一次 read 就停,剩余数据会滞留,下次再无通知,直接丢数据。
核心原则:只要 read 返回 > 0,就继续读;一旦返回 -1 且 errno == EAGAIN 或 EWOULDBLOCK,才表示当前缓冲区已空。
示例片段(简化版):
ssize_t n;
char buf[4096];
while ((n = read(fd, buf, sizeof(buf))) > 0) {
// 处理数据
}
if (n == -1 && errno != EAGAIN && errno != EWOULDBLOCK) {
// 真正出错,如连接断开
close(fd);
}
- 不能用
if (n > 0)替代while,这是 ET 最常见 bug 来源 -
write同理:若返回值 len,需保存偏移、继续写,直到全部发出或遇到EAGAIN - 务必检查
errno,Linux 下EAGAIN和EWOULDBLOCK值相同,但语义不同,保守起见两个都判
EPOLLONESHOT 配合 ET 可避免事件重入
多线程处理同一个 fd 时,ET + 共享 epoll_fd 容易出现多个线程同时收到同一事件(尤其在 read 未清空缓冲区前又触发了新数据到达)。这时用 EPOLLONESHOT 能强制事件只投递一次,后续需手动 epoll_ctl(..., EPOLL_CTL_MOD, ...) 重新启用。
它和 EPOLLET 不冲突,可以共存:
ev.events = EPOLLIN | EPOLLET | EPOLLONESHOT; epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &ev);
- 启用
EPOLLONESHOT后,每次事件处理完必须调用epoll_ctl改回监听状态,否则该 fd 彻底失联 - 注意并发安全:多个线程可能同时尝试
MOD,需加锁或用原子操作协调 - 单线程场景下一般不需要,但高负载、长耗时协议解析(如 TLS 握手)中能防止重复调度
epoll_wait 返回后不要假设所有就绪 fd 都能立刻读写
即使 epoll_wait 返回了某个 fd 在 EPOLLIN 状态,也不代表此刻 read 一定不阻塞或一定能读到数据——比如 TCP FIN 已到,但应用层还没收到完整包;或者对端发了 RST,fd 实际已不可读。
所以每次 read / write 后都必须检查返回值和 errno,而不是依赖 epoll_wait 的结果做乐观假设。
-
read返回 0 表示对端关闭连接,应清理资源 -
read返回 -1 且errno == ECONNRESET或EPIPE,说明连接异常中断 - 即使用了 ET,仍要处理
EPOLLHUP和EPOLLERR事件,它们不遵循 ET/LT 区分,总是立即上报
ET 的关键不在“更高效”,而在“更严格”:它把事件通知的边界交还给用户,也把边界判断的责任一并移交。少一次 while,就可能漏一整段数据;少一个 errno 判断,就可能让连接卡死在半途。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











