et模式下不能只读一次,因为其仅在状态跳变时通知一次;若未读尽缓冲区数据且无新数据到达,epoll_wait将不再触发,导致残留数据无法被读取,必须循环读至eagain/ewouldblock。

epoll ET模式下为什么不能只读一次就返回
因为ET(Edge-Triggered)模式只在文件描述符状态**从不可读变为可读**时通知一次。如果一次 read() 没把内核缓冲区数据全取完,后续又没新数据到达,epoll_wait() 就再也不会触发该事件——你的连接会“卡住”,看似没数据,实则还有残留。
循环读取直到EAGAIN的正确写法
核心是:收到 EPOLLIN 事件后,用循环 + 非阻塞 read() 把 socket 缓冲区清空,直到返回 -1 且 errno == EAGAIN 或 EWOULDBLOCK(二者等价)。
关键前提:socket 必须设为非阻塞(fcntl(fd, F_SETFL, O_NONBLOCK)),否则最后一次 read() 会阻塞住。
常见错误写法:
– 只调一次 read() 就结束;
– 忘记检查 errno,把 -1 当成普通错误直接关闭连接;
– 在循环里没处理 0 返回值(对端已关闭)。
实操建议:
- 每次
read()后检查返回值:if (n == 0)→ 对端关闭,应close(); -
if (n > 0)→ 正常收数据,继续循环; -
if (n == -1)→ 看errno:if (errno == EAGAIN || errno == EWOULDBLOCK)→ 清空完成,跳出循环;其他错误(如ECONNRESET)需按需处理。
一个典型循环读取片段(C++)
ssize_t n;
char buf[65536];
while ((n = read(fd, buf, sizeof(buf))) > 0) {
// 处理 buf[0..n)
on_message(fd, buf, n);
}
if (n == -1) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 缓冲区已空,ET本轮处理结束
} else {
// 真实错误,比如 ECONNRESET、EIO
handle_error(fd, errno);
}
} else if (n == 0) {
// 对端 shutdown 或 close
close_connection(fd);
}
为什么不用MSG_DONTWAIT替代O_NONBLOCK
虽然 recv(fd, buf, len, MSG_DONTWAIT) 也能实现单次非阻塞读,但它每次调用都要进内核做标志检查,开销略高;而全局设 O_NONBLOCK 后,所有 I/O 默认非阻塞,更符合 ET 模式“批量消费”的语义,也避免漏掉 send()、connect() 等调用的阻塞风险。
另外注意:MSG_DONTWAIT 在某些旧内核或特定协议栈上行为不一致,O_NONBLOCK 兼容性更稳。
read() 返回的只是字节流,你得自己按协议(比如包头长度字段、分隔符、固定帧长)去拆包。这个逻辑一旦写错,哪怕循环读对了,应用层照样粘包或截断。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











