当 TCP 连接中的接收方应用程序不调用 recv()
(或类似的接收函数,如 read()
) 时,会发生一系列由 TCP 协议栈自动处理的情况,核心是 TCP 的流量控制机制会介入。
我们假设有两个主要部分:发送方 (Sender) 和 接收方 (Receiver)。每一方都有应用程序层 (App) 和内核 TCP 层 (Kernel)。
阶段 1: 正常数据传输
1、接收方 App 正常调用
recv()
2、接收方 Kernel 的接收缓冲区 (Recv Buffer) 有足够空间
3、接收方 Kernel 通告一个较大的接收窗口 (
rwnd
)
[概念图示 - 阶段 1]
+-----------------+ +-----------------+
| Sender Kernel | | Receiver Kernel |
| Send Buffer: | -----> | Recv Buffer: | <---- Receiver App reads
| [Some data] | | [Some data] |
+-----------------+ | Capacity: High || Free: High |+-----------------+|-----> ACK (rwnd = High) back to Sender
阶段 2: 接收方 App 不调用 recv()
,缓冲区开始填满
1、发送方继续发送数据
2、接收方 Kernel 接收数据,存入 Recv Buffer
3、接收方 App 没有调用
recv()
读取数据4、Recv Buffer 中的数据越积越多,可用空间减少
5、接收方 Kernel 发送的 ACK 中
rwnd
值开始减小
[概念图示 - 阶段 2]
+-----------------+ +-----------------------+
| Sender Kernel | | Receiver Kernel |
| Send Buffer: | -----> | Recv Buffer: | <-- App NOT reading
| [Data waiting] | | [data3|data4|.......] |
+-----------------+ | Capacity: High || Free: Low / Small |+-----------------------+|-----> ACK (rwnd = Small) back to Sender
阶段 3: 接收缓冲区满,通告零窗口 (Zero Window)
1、发送方又发送了一个小数据段,正好填满了接收缓冲区
2、接收方 Kernel 接收最后的数据,Recv Buffer 满了
3、接收方 Kernel 发送 ACK,
rwnd
值为 0
[概念图示 - 阶段 3]
+-----------------+ +-----------------------+
| Sender Kernel | | Receiver Kernel |
| Send Buffer: | --X--> | Recv Buffer: | <-- App NOT reading
| [More data...] | (Stops)| [data3|data4|data5|FULL] |
+-----------------+ | Capacity: High || Free: Zero |+-----------------------+|-----> ACK (rwnd = 0) back to Sender
阶段 4: 发送方停止发送,进入持续状态 (Persist State)
1、发送方 Kernel 收到
rwnd=0
的 ACK2、发送方 Kernel 停止发送新的数据段 (Send Buffer 中的数据等待)
3、如果发送方 App 继续调用
send()
,数据会堆积在 Send Buffer;如果 Send Buffer 也满了,send()
调用会阻塞 (或返回 EAGAIN/EWOULDBLOCK)4、发送方 Kernel 启动 Persist Timer
阶段 5: 窗口探测 (Window Probe)
1、Persist Timer 超时
2、发送方 Kernel 发送一个小的 Window Probe 包 (通常是 1 字节数据或纯 ACK)
3、接收方 Kernel 必须响应这个 Probe,回复当前的
rwnd
(此时仍然是 0)4、发送方 Kernel 收到
rwnd=0
的 ACK,重置 Persist Timer (超时时间通常会指数增长)
阶段 6: 接收方 App 调用 recv()
,恢复传输
1、接收方 App 终于调用
recv()
,从 Recv Buffer 读取数据2、Recv Buffer 腾出空间
3、当接收方 Kernel 下次发送 ACK 时 (可能是对 Window Probe 的响应,或对重传数据的响应),会包含一个非零的
rwnd
4、发送方 Kernel 收到非零
rwnd
的 ACK5、发送方 Kernel 恢复数据发送 (根据新的
rwnd
大小)6、如果发送方 App 的
send()
之前被阻塞,现在可能会解除阻塞
[概念图示 - 阶段 6]
+-----------------+ +-----------------------+
| Sender Kernel | | Receiver Kernel |
| Send Buffer: | -----> | Recv Buffer: | <---- App finally reads!
| [Sending again] | | [.......|data5| Free ] |
+-----------------+ | Capacity: High || Free: Non-Zero |+-----------------------+|-----> ACK (rwnd = Non-Zero) back to Sender
总结:
接收方不调用 recv()
主要导致:
-
接收缓冲区填满。
-
TCP 流量控制机制生效,接收方通告零窗口 (
rwnd=0
)。
-
发送方暂停数据发送,并进入持续状态,定期发送窗口探测包。
-
连接本身通常不会因此立即断开(除非有其他超时机制介入,如 Keep-Alive 超时),但数据传输会完全停止,直到接收方应用程序读取数据,接收缓冲区腾出空间,并通告非零窗口。
- 发送方应用程序的
send()
调用最终可能会阻塞或失败。
这种情况是 TCP 健壮性的体现,它确保了快速的发送方不会淹没慢速的接收方,防止了数据丢失和资源耗尽。但也可能导致应用程序层面的"卡死"或性能下降,如果接收方长时间不处理数据的话。