必须为epoll相关socket设置非阻塞模式,否则accept()/recv()可能永久阻塞;epoll仅通知事件,不改变io默认阻塞行为;需对监听socket及每个accept返回的连接fd调用fcntl设o_nonblock,并遍历epoll_wait返回的所有就绪事件。

epoll_create()之后必须先设置非阻塞模式
直接对epoll_create()返回的句柄调用epoll_ctl()注册监听套接字,但没设O_NONBLOCK,会导致后续accept()或recv()在无数据时永久阻塞,整个事件循环卡死。Linux下socket默认是阻塞的,epoll只是通知“有事发生”,不改变IO行为本身。
实操建议:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 创建监听socket后立即调用
fcntl(fd, F_SETFL, O_NONBLOCK) - 对每个
accept()返回的新连接fd也必须设非阻塞——否则一个慢客户端就拖垮全部连接 - 检查
fcntl()返回值,失败时close()该fd并跳过
epoll_wait()返回后要遍历所有就绪事件,不能只处理第一个
常见错误是把epoll_wait()返回的nready当“只有一个事件”,只取events[0]处理。实际nready可能大于1,尤其高并发时多个连接同时发来数据,漏掉后面的events[i]会导致连接假死或数据丢失。
实操建议:
- 用
for (int i = 0; i 完整遍历<code>struct epoll_event*数组 - 对每个
events[i].data.fd,先判断是否为监听fd(即是否等于listen_fd),再决定是accept()还是recv() - 收到
EAGAIN或EWOULDBLOCK时直接continue,不报错也不关闭连接
recv()返回0意味着客户端已关闭连接,必须close()对应fd
echo服务器里,如果recv()返回0,说明对方调用了close()或进程退出,TCP FIN已到达。此时若不close()本端fd,该fd会一直留在epoll中,下次就绪又触发recv(),反复返回0,形成空转消耗CPU。
实操建议:
if (n == 0) { close(conn_fd); epoll_ctl(epoll_fd, EPOLL_CTL_DEL, conn_fd, nullptr); }- 务必在
epoll_ctl(..., EPOLL_CTL_DEL, ...)之后再close(),否则内核可能仍在投递事件 - 不要忽略
epoll_ctl()返回值:失败时记录日志并close(),避免fd泄漏
send()可能只发出部分数据,必须循环直到写完或出错
echo服务器调用send()回传数据时,返回值可能小于待发送字节数(比如缓冲区满)。若直接认为“已发完”就去等待下一次事件,客户端收不到完整响应,表现为半截消息或超时断连。
实操建议:
- 维护每个连接的待发送缓冲区(如
std::string out_buf),每次send()后删掉已发出的部分 - 若
send()返回-1且errno == EAGAIN,说明内核缓冲区满,暂时无法发送,需等EPOLLOUT就绪再试 - 注册连接fd时用
EPOLLIN | EPOLLOUT(边缘触发ET模式下更需如此),但首次只关心EPOLLIN,有数据要发时才关注EPOLLOUT
recv()或send(),不代表一次就能读完或写完。边界条件全靠errno和返回值判断,少一个if (n == 0)或漏一次EPOLL_CTL_DEL,服务跑半天才发现连接数缓慢上涨。C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










