Linux epoll ET 模式下为什么必须循环读到 EAGAIN
Linux epoll 使用 ET(edge-triggered,边沿触发)时,事件通知告诉你的不是“以后每次都还有数据”,而是“这个文件描述符从某个状态变成了可读或可写”。如果一次只读走一部分,剩余数据可能不会再次产生边沿,事件循环就会把连接暂时忘掉。实际处理方式是:把 fd 设为非阻塞,在一次事件处理里持续读取,直到 read 或 recv 返回 EAGAIN 或 EWOULDBLOCK。
- ET 关注状态变化;残留在接收缓冲区的数据不一定重新唤醒
epoll_wait。 - 非阻塞读循环中,正数表示继续处理,0 表示对端关闭,
EAGAIN表示本轮 I/O 已耗尽。 - “读到 EAGAIN”解决的是事件丢失;就绪队列和轮转预算解决的是单个 fd 长时间占用 CPU。
ET 的风险不是少读一次,而是留下一个不会主动提醒你的状态
假设 socket 缓冲区里来了 8 KB 数据,epoll_wait 返回一次可读事件,业务代码只调用一次 read(fd, buf, 1024)。读完 1 KB 后,缓冲区还剩 7 KB。LT 模式会因为“仍然可读”继续报告;ET 模式更关心从不可读到可读的那次变化,后续等待可能看不到这 7 KB。
所以 ET 的第一条边界是:收到事件后,应把 fd 视为“持续就绪”,直到一次非阻塞读写明确返回 EAGAIN。这也是为什么 ET 通常必须配合非阻塞 fd;如果 fd 仍是阻塞模式,循环读到最后可能卡在下一次系统调用,整个事件循环无法服务其他连接。

非阻塞读取要把 EAGAIN 当成“本轮完成”
下面的处理函数只展示读取边界,不负责协议拆包。协议层应把读取到的字节追加到连接自己的输入缓冲区,再根据长度字段或分隔符判断完整消息。
#include
#include
static int drain_read(int fd, char *buf, size_t cap) {
for (;;) {
ssize_t n = recv(fd, buf, cap, 0);
if (n > 0) {
// 把 n 个字节追加到连接的协议缓冲区,再继续排空 socket。
append_to_connection_buffer(buf, (size_t)n);
continue;
}
if (n == 0) {
// 返回 0 表示对端有序关闭,调用方应移出 epoll 并 close(fd)。
return 0;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 当前内核接收缓冲区已读空,本轮事件处理可以结束。
return -2;
}
// 其他 errno 是真实 I/O 错误,不能伪装成“暂时没有数据”。
return -1;
}
}
关键判断顺序不能反:0 是关闭,EAGAIN 是暂时读空;前者要清理 fd,后者要保存连接状态并回到事件循环。示例里的 append_to_connection_buffer 代表协议层自己的半包缓冲,不应把未完成消息丢在临时数组里。
| 返回值 | 含义 | 处理动作 |
|---|---|---|
n > 0 | 读到数据 | 追加到协议缓冲区,继续读 |
n == 0 | 对端关闭写端 | 取消监听并释放连接资源 |
EAGAIN/EWOULDBLOCK | 本轮没有更多数据 | 保存状态,等待下一次事件 |
| 其他错误 | 真实 I/O 失败 | 记录 errno,按连接策略关闭或恢复 |
读到 EAGAIN 之后,还要防住两个工程风险
第一个风险是“排空一个 fd”变成无限工作。对端可以持续发送数据,使你的循环长期拿不到 EAGAIN,其他连接因此饥饿。常见控制方式是维护就绪队列:事件到来时只把连接标记为 ready,每轮给它一个字节数或消息数预算;预算用完就暂存连接,轮转处理其他 ready fd。
第二个风险是关闭后的事件缓存。一次 epoll_wait 可能返回多个事件,前面的事件处理已经关闭了后面某个 fd。连接对象应有 removed/closed 标记,处理缓存中的后续事件前先检查它,避免对已经释放的对象继续读写。若使用 EPOLLONESHOT,处理完后还必须通过 epoll_ctl(..., EPOLL_CTL_MOD, ...) 重新激活 fd,否则连接会保持失活。

用这张检查清单判断 ET 读取是否可靠
- 注册
EPOLLET的 fd 是否确实设置了O_NONBLOCK? - 一次事件是否持续读到
EAGAIN,而不是只调用一次read? - 是否把
0、EAGAIN和其他错误分开记录? - 协议半包是否保存在连接状态中,而不是丢在临时缓冲区?
- 是否给高流量连接设置轮转预算,并在关闭后使事件缓存失效?
对流式 fd,Linux 的手册也提到,若能保证 fd 始终是 stream-oriented,读取量小于请求量可以帮助判断当前 I/O 空间已耗尽;但通用事件循环更稳妥的约定仍是非阻塞读到 EAGAIN。这样不会把“短读”错误地套用到数据报或其他边界敏感的 fd 上。
常见问题
ET 模式一定要每次读到 EAGAIN 吗?
对需要持续消费的非阻塞 fd,这是最清晰、最通用的边界。流式 fd 在有严格前提时也可以用短读判断耗尽,但不能把这个特例当成所有 fd 的规则。
为什么不能继续使用阻塞 socket?
因为最后一次读取可能等待未来数据,阻塞当前线程;ET 事件循环通常还要服务其他 fd,所以应使用非阻塞模式。
读到 EAGAIN 后要不要立刻再次调用 epoll_wait?
可以回到事件循环,但先保存半包、写缓冲区和连接状态。若使用 ONESHOT,还要在状态可继续处理时重新 arm。
Python logging RotatingFileHandler 轮转后如何保留最近几份
- 上一篇
- Python logging RotatingFileHandler 轮转后如何保留最近几份
- 下一篇
- Go 编译器提示 cannot use T as comparable 时怎么收紧约束
-
- 文章 · linux | 2小时前 | oom · cgroup · Linux内存 · 容器运维 · Linux cgroup v2 memory.events memory.max memory.high
- Linux cgroup v2 memory.max 设置后进程为什么还能短暂超出
- 352浏览 收藏
-
- 文章 · linux | 4小时前 |
- Linux tmpfs 使用率高时怎么判断是缓存还是空间不足
- 468浏览 收藏
-
- 文章 · linux | 7小时前 |
- Linux ip route 里 metric 相同时怎么判断默认路由
- 277浏览 收藏
-
- 文章 · linux | 8小时前 |
- Linux ss 查看监听端口时怎么对应到具体进程
- 481浏览 收藏
-
- 文章 · linux | 9小时前 | Linux · systemd · 日志排查 · systemd journalctl unit boot日志
- Linux journalctl 怎么只看某个 unit 的本次启动日志
- 288浏览 收藏
-
- 文章 · linux | 10小时前 |
- Linux find 删除大量文件时怎么避免参数列表过长
- 232浏览 收藏
-
- 文章 · linux | 13小时前 |
- Linux io_uring_disabled 设置变化会影响哪些进程
- 480浏览 收藏
-
- 文章 · linux | 16小时前 | Linux · 文件系统 · inotify · inotify IN_Q_OVERFLOW Linux文件监控
- Linux inotify 队列溢出后怎么恢复完整扫描
- 456浏览 收藏
-
- 文章 · linux | 17小时前 |
- Linux inotify 监控目录时怎么处理事件合并和重复通知
- 145浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 32次使用
-
- SuperCLUE
- SuperCLUE是权威的中文大语言模型综合评测基准,涵盖语言理解、知识应用、AI Agent智能体及安全性等12项核心能力。通过多轮对话与客观测试,定期发布榜单与技术报告,为模型研发、优化及行业选型提供科学依据。
- 187次使用
-
- C-Eval
- 深入了解C-Eval中文评估套件,涵盖52个学科与4级难度。本文详解其功能特点、Zero-shot/Few-shot使用方法及代码示例,助您全面评测LLM中文理解与泛化能力。
- 126次使用
-
- AI Prompt Library
- 探索AI Prompt Library免费资源库,涵盖营销、写作及多场景AI提示词。兼容ChatGPT、Claude等工具,一键复制优化输出,提升工作效率。
- 50次使用
-
- Generrated
- Generrated汇集9300+张DALL·E生成图像及对应提示词,支持查看完整图集、对比DALL·E 2与3版本差异,是AI绘图新手学习Prompt设计与获取创作灵感的实用工具。
- 33次使用
-
- 深入了解Golang网络编程Net包的使用
- 2023-01-23 215浏览
-
- 详解golang执行Linuxshell命令完整场景下的使用方法
- 2023-01-07 426浏览
-
- go程序部署到linux上运行的实现方法
- 2023-01-02 387浏览
-
- Linux系统下Go语言开发环境搭建
- 2023-01-07 242浏览
-
- 使用golang获取linux上文件的访问/创建/修改时间
- 2022-12-31 238浏览

