send/recv的阻塞本质是用户空间与内核缓冲区间数据拷贝的等待逻辑:阻塞模式下,send等待缓冲区有足够空间才完成拷贝,recv等待接收缓冲区有数据才开始拷贝;非阻塞模式下二者均立即返回,由调用方处理eagain或循环重试。

Linux 中 send/recv 的阻塞行为,本质不是“发出去”或“收进来”,而是数据在用户空间与内核缓冲区之间拷贝时的等待逻辑。理解这一点,才能避开常见误区——比如以为 send 返回成功就等于对方收到了数据。
send 在阻塞与非阻塞模式下的关键差异
send 的作用始终是:把应用层 buf 中的数据,尽可能拷贝到内核 TCP 发送缓冲区(sk->sk_write_queue)。它不参与实际网络发送,那是协议栈后续做的事。
- 阻塞模式下:如果发送缓冲区剩余空间
- 非阻塞模式下:只要缓冲区有空,就尽最大努力拷——可能只拷 1 字节、也可能拷全部;若缓冲区全满,直接返回 -1 并设 errno=EAGAIN/EWOULDBLOCK。不会等,也不保证原子性。
- 特别注意:即使 send 成功返回,若之后网络断开或对端 RST,下一次 socket 调用(如 send 或 recv)才可能暴露错误;TCP 不会在 send 时校验链路连通性。
recv 的阻塞逻辑取决于接收缓冲区状态
recv 的任务是:把内核 TCP 接收缓冲区(sk->sk_receive_queue)里的数据,拷贝到用户提供的 buf 中。是否阻塞,只看这个缓冲区此刻有没有可用数据。
Linux AI 服务器 — 将 Linux 服务器转换为本地 AI 推理集群。使用 systemd、NVIDIA CUDA 实现无头 Linux AI,零 GUI 开销。
- 阻塞模式下:若接收缓冲区为空,进程挂起休眠,直到有数据到达(含 FIN)或连接异常(如对端关闭、超时)。一旦有数据,哪怕只有 1 字节,也会立即拷贝并返回实际字节数;不会等到 buf 满才返回。
- 非阻塞模式下:缓冲区空则立刻返回 -1 + errno=EAGAIN/EWOULDBLOCK;有数据则拷贝 min(len, 可用字节数),返回实际拷贝量。调用方需自行循环重试或结合事件驱动机制处理。
- recv 返回 0 表示对端已正常关闭连接(FIN 到达且本端已读完所有数据),这不是错误,是连接终止的明确信号。
影响阻塞行为的底层参数与配置
阻塞与否由 socket 文件描述符的标志位决定,但实际表现还受内核缓冲区大小和协议栈状态制约:
- 发送缓冲区大小可通过 setsockopt 设置 SO_SNDBUF,但受 net.core.wmem_max 限制;过小会导致阻塞更频繁,过大则增加内存占用和延迟。
- 接收缓冲区大小对应 SO_RCVBUF,同样受 net.core.rmem_max 约束;若应用 recv 不及时,缓冲区满后对端将受 TCP 流量控制限制(窗口为 0),进而影响 send 行为。
- 默认创建的 socket 是阻塞的;设为非阻塞需显式调用 fcntl(sockfd, F_SETFL, O_NONBLOCK) 或创建时指定 SOCK_NONBLOCK。
- UDP socket 的 send/recv 不涉及可靠传输队列,因此 sendto/recvfrom 在阻塞模式下也几乎不阻塞(除非系统资源耗尽),这点与 TCP 有本质区别。
如何判断当前是否该用阻塞还是非阻塞
选择依据不是“习惯”,而是服务模型和性能目标:
- 单连接、交互简单(如 CLI 工具、短连接 HTTP 客户端):阻塞模式代码简洁,无需轮询或事件循环。
- 高并发服务器(如 Web 服务、IM 后端):必须用非阻塞 + I/O 多路复用(epoll/select);否则每个连接都占一个线程/进程,资源消耗不可控。
- 需要精确控制超时(如金融报文、心跳检测):非阻塞配合 epoll_wait 或 setsockopt(SO_RCVTIMEO/SO_SNDTIMEO) 更可靠;阻塞模式下仅靠 alarm() 难以精准干预 socket 调用。
- 流式大文件传输:可混合使用——用阻塞 recv 避免忙等,但需搭配 SO_RCVLOWAT 控制最小触发阈值,减少系统调用次数。










