答案是:必须循环accept直到EAGAIN、epoll_wait遇EINTR需重试、ET模式下read/write须循环至EAGAIN,且所有socket均设非阻塞。否则将导致连接堆积、事件丢失或线程阻塞。

直接上手写 epoll 服务端,不封装、不依赖框架,能跑通且避开常见 crash 点——关键不是“会不会调用 epoll_create”,而是“accept 漏掉 EAGAIN 会导致连接堆积”“epoll_wait 返回 -1 但 errno == EINTR 必须重试”“EPOLLET 下不循环 read 直到 EAGAIN 会丢数据”。这些才是实际写时卡住你的地方。
怎么初始化 epoll 实例并监听 socket
不要传大数字给 epoll_create,Linux 2.6+ 已忽略 size 参数,填 1 就行;真正要设的是 socket 的非阻塞标志,否则 accept 和 recv 会阻塞整个事件循环。
-
epoll_create(1)返回一个 fd,失败时返回 -1,记得检查 - 监听 socket(
listenfd)必须用fcntl(listenfd, F_SETFL, O_NONBLOCK)设为非阻塞 - 注册时用
EPOLLIN | EPOLLET(边缘触发)更高效,但要求后续读写必须一次性处理完就绪数据 -
epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev)中的&ev要把ev.data.fd = listenfd显式赋值,否则epoll_wait返回后无法识别事件来源
为什么 accept 必须循环直到 EAGAIN
一个 listenfd 上可能同时有多个客户端完成三次握手,但一次 accept 只取一个。不循环会把其余连接留在内核队列里,表现为“连接数上不去”或“新连接迟迟没回调”。
- 每次
accept后检查返回值:if (clientfd == -1 && errno == EAGAIN) break;—— 这是退出循环的唯一安全信号 - 如果
accept返回 -1 且errno是EMFILE或ENFILE,说明进程或系统级 fd 耗尽,需降级或告警,不能忽略 - 新
clientfd也必须设为非阻塞,否则一次recv阻塞,整个 loop 就卡死
EPOLLET 模式下 read/write 怎么写才不丢数据
LT 模式会反复通知就绪,ET 只通知一次。选 ET 是为了减少内核到用户态的事件拷贝次数,但代价是:你必须自己保证把缓冲区数据全部读完或写完。
Linux系统管理专家,覆盖12大模块:用户权限、SSH、存储、网络、systemd、防火墙、日志监控、备份恢复、TLS证书、Ansible、容器、IaC。提供配置、验证、加固、监控、备份、自动化、故障排查、回滚闭环。关键词:useradd、sudo、sshd_config、chmod、SEL...
立即学习“C++免费学习笔记(深入)”;
- 对每个
clientfd调用recv时,必须循环直到返回值 ≤ 0:while ((n = recv(fd, buf, sizeof(buf)-1, 0)) > 0) { /* 处理 */ } if (n == 0) { /* 对端关闭,该 close(fd) */ } else if (n == -1 && errno == EAGAIN) { /* 本次数据已清空,跳出循环 */ } - 写操作同理:
send返回 -1 且errno == EAGAIN时,不能直接放弃,应把未发送数据暂存,等EPOLLOUT再次触发时继续发 - 千万别在 ET 模式下只调一次
recv就完事——这是丢包最常见原因
epoll_wait 出错或超时时该怎么处理
epoll_wait 返回 -1 不等于出错,errno == EINTR 是信号中断,应重试;返回 0 表示超时,不是错误,正常继续循环即可。
- 超时参数建议设为
-1(永久阻塞),除非你有定时任务要驱动,否则加毫秒超时只会增加无谓的 CPU 轮询 - 返回 -1 且
errno == EBADF说明某个被监控的 fd 已关闭但没从 epoll 删除,必须先epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr)再close(fd) - 返回 >0 时,遍历
events[0..n-1],注意区分events[i].data.fd是listenfd还是clientfd,别把 accept 逻辑套到普通连接上
真正难的从来不是把四个函数串起来,而是每个系统调用后对 errno 的分支判断是否完整、非阻塞语义是否贯彻到底、ET 模式下缓冲区边界是否守得住。漏掉任意一处,服务跑一小时可能才暴露问题——比如凌晨三点连接突然不响应,查日志发现是某次 accept 后没清空队列,积压了 200 个待处理连接,最终触发 EMFILE。

















