必须检查epoll_create1()返回值,失败时返回-1,常见因文件描述符超限;监听socket需设EPOLLIN|EPOLLET并配非阻塞I/O;epoll_wait()后须遍历全部nready个就绪事件。

epoll_create() 创建实例后必须检查返回值
epoll 不是开箱即用的黑盒,epoll_create() 或 epoll_create1() 失败时返回 -1,常见原因是进程打开文件描述符数超限(RLIMIT_NOFILE)或内核未启用 epoll 支持(极少见)。不检查直接往下用,后续 epoll_ctl() 会报 Bad file descriptor,但错误源头其实早在第一步。
实操建议:
- 始终用
if (epfd == -1) { perror("epoll_create1"); return -1; }检查 - 调试时可用
ulimit -n查看当前限制,必要时用ulimit -n 65536临时调高(注意:需在启动程序前设置) - 优先用
epoll_create1(EPOLL_CLOEXEC),避免 fork 后子进程意外继承 epoll fd
epoll_ctl() 添加监听 socket 时要设 EPOLLIN | EPOLLET
只加 EPOLLIN 能工作,但默认是水平触发(LT),在高并发下容易反复唤醒——比如一次 read() 只读了部分数据,内核发现缓冲区还有可读数据,下次 epoll_wait() 立刻又返回该 fd。这会导致大量无效唤醒,吞吐下降。
实操建议:
- 对监听 socket(
listen_fd)和所有客户端 socket,一律用EPOLLIN | EPOLLET启用边缘触发(ET) - ET 模式下,必须循环
read()直到返回-1且errno == EAGAIN或EWOULDBLOCK,否则会漏数据 - 别忘了给 socket 设置非阻塞:用
fcntl(fd, F_SETFL, O_NONBLOCK),ET 模式强制要求非阻塞 I/O
epoll_wait() 返回后必须遍历全部就绪事件
很多人误以为 epoll_wait() 返回的是“一个就绪 fd”,其实它返回的是就绪事件数量 nready,而事件数组 events[] 里可能有多个元素,每个对应一个 fd 和其事件类型(EPOLLIN、EPOLLOUT、EPOLLERR 等)。
实操建议:
- 必须用
for (int i = 0; i 遍历events数组,不能只处理events[0] - 对每个
events[i].data.fd,先判断是否为监听 socket:是则accept()新连接;否则按events[i].events & EPOLLIN判断是否可读 - 若同时注册了
EPOLLOUT(例如发大包时),需单独处理写就绪,避免和读逻辑混在一起
close() 前必须先 epoll_ctl(EPOLL_CTL_DEL)
直接 close(fd) 不会自动从 epoll 实例中移除该 fd。如果之后又分配到相同 fd 编号(Linux 文件描述符复用很常见),旧事件可能误触发到新 fd 上,造成逻辑错乱甚至崩溃。
实操建议:
- 每次关闭 socket 前,务必调用
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, nullptr) - 即使
epoll_ctl()返回 -1 也不影响close(),但最好检查errno == ENOENT(表示已不在 epoll 中),其余错误需记录告警 - 把 fd 清零或置为 -1,并在业务逻辑中加守卫判断(如
if (fd == -1) return;),防止重复 close
ET 模式 + 非阻塞 + 循环读写 + 显式 del + 错误检查,这五点缺一不可。少做一步,问题可能隔几小时或几天才暴露,比如连接突增时缓冲区积压、ET 下漏读、fd 复用冲突——这些都不是编译错误,而是运行时毛刺,排查成本远高于写的时候多敲几行。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











