send()返回eagain说明内核发送缓冲区已满,需应用层维护异步发送队列并监听epollout事件,在可写时续发未完成数据,而非忽略、重试或增大内核参数。

send() 返回 EAGAIN 时,说明内核发送缓冲区已满,但 socket 是非阻塞的
这不表示网络断了,也不是你代码写错了,而是 TCP 发送队列(sk->sk_write_queue)被填满、内核暂时无法拷贝新数据。常见于高吞吐场景或对端接收太慢(比如没调用 recv()、接收缓冲区溢出、网络卡顿)。关键点是:你必须自己处理这个“暂时发不出去”的状态,不能忽略或直接重试。
正确做法是把未发送完的数据缓存起来,等 socket 可写再重试
不能简单循环 send(),也不能丢弃数据。你需要一个应用层发送缓冲区(比如 std::deque<:vector>></:vector> 或环形 buffer),并在 epoll_wait() 监听 EPOLLOUT 事件触发时尝试续发。
- 调用
send()前先检查应用层缓冲区是否为空;为空才发新数据,否则只处理缓存 - 若
send()返回-1且errno == EAGAIN或EWOULDBLOCK,把剩余未发送的字节追加进应用层缓冲区 - 确保 epoll fd 已注册
EPOLLOUT(即使初始不可写也要注册,否则第一次满之后再也收不到可写通知) - 收到
EPOLLOUT后,只尝试发送缓存头部数据,发完一部分就 pop,直到缓存空或再次遇到EAGAIN
容易踩的坑:EPOLLOUT 的触发逻辑和边缘情况
EPOLLOUT 不是“可以发一点”,而是“至少能发 1 字节”。它会在以下任一时刻触发:socket 刚建立连接成功、上次 send() 阻塞后缓冲区有空闲、对端 ACK 推进滑动窗口。但注意:
- 如果应用层缓冲区为空,且你没在
EPOLLOUT回调里主动发数据,epoll 可能持续通知(busy loop),需在发送前判断是否有数据待发 - 不要在每次
EPOLLOUT时都重新注册监听——它默认是水平触发(LT),除非你显式用EPOLLET - 当对端关闭连接(RST 或 FIN),
send()可能返回EPIPE或EINVAL,此时应清理连接,而不是当成EAGAIN处理 - Linux 默认 socket 发送缓冲区约 212992 字节(
net.ipv4.tcp_wmem),小包高频发送时很容易打满,别依赖增大内核参数来掩盖设计问题
一个最小可行的 send 续传逻辑片段
ssize_t result = send(sockfd, buf + sent, len - sent, MSG_NOSIGNAL);
if (result > 0) {
sent += result;
if (sent == len) return true; // 全部发出
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
// 缓存剩余数据:push_back({buf + sent, len - sent})
enable_epollout(); // 确保 EPOLLOUT 已注册
return false;
} else {
// 其他错误:ECONNRESET、EPIPE、EPIPE 等,该关连接了
return false;
}
真正麻烦的从来不是怎么发,而是怎么安全地把“发了一半”这个状态在多线程/多事件间保持一致——如果你用了 io_uring 或 libuv,它们内部已经封装了这类逻辑,但底层原理没变:数据没进内核,就得你自己管着。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











