每个TCP套接字有一个发送缓冲区,这是由内核维护的,用来缓存应用层写入的数据。我们可以使用 SO_SNDBUF 套接字选项来更改缓冲区大小,但内核会翻倍(包含协议开销),所以应用看到的实际容量比你设置的更大。
当某个应用进程调用 write 时,内核从该应用程序的缓存区复制所有数据到所写套接字的发送缓冲区。
如果该套接字的发送缓冲区容不下该进程的所有数据(或是应用进程的缓冲区大于套接字的发送缓冲区,或是套接字的发送缓冲区已经有其他数据),该应用进程将被投入睡眠。
这里假设套接字是阻塞的,它是通常的默认设置。内核将不从 write 系统调用返回,开始阻塞套接字:
- write 会挂起
- 等待发送缓冲区腾出空间
- 然后继续复制剩余数据
- 直到全部复制完成才返回
write 返回仅表示:
- 数据已经复制进入内核缓冲区
- 应用缓冲区可以安全重用或释放
但不表示:
- 对端 TCP 收到
- 对端应用层收到
- 数据已可靠送达
因为实际网络传输还在进行,写一个TCP套接字的 write 调用成功返回仅仅表示我们可以重新使用原来的应用进程缓冲区,并不表明对端的TCP或应用进程已接收到数据。
write() 返回时:
数据在本机内核 TCP 缓冲区中
不保证:
数据已经经过网络
数据已到达对端 TCP
数据已被对端应用 read() 读取
write 成功后,如果电脑马上宕机(断电/崩溃),已经写入内核发送缓冲区但尚未通过网络发送、或尚未获得对端 ACK 的数据会丢失。
对端 TCP 端会:
- 在一段时间内继续等待数据(TCP 有重传和超时机制)
- 最终因为没收到你的重传 → 超时
- 触发 ETIMEDOUT 或 ECONNRESET,并关闭连接
对端应用层:
- read() 最终返回 0(连接关闭)或错误(reset)
对端无法知道是:崩溃/拔网线/断电/杀进程,TCP 不区分这些情况。
在分布式系统中,这种问题叫不确定提交。本质是应用 write() 或网络发送成功,但应用在收到 DB 或服务端的响应之前就崩溃,导致无法知道操作是否成功执行。
例如:
- SQL 发出了但你没收到 commit 结果
- RPC 调用了但你没收到返回
- MQ 发送成功但你没收到 broker ACK
可选的解决方案有:
- 幂等
- Outbox Pattern(本地事务 + 异步可靠投递)
- 事务消息(半消息),这是RocketMQ、Kafka、Pulsar 提供的企业级特性
- 两阶段提交(Two Phase Commit, 2PC)



