TCP 滑动窗口与流量控制
更新时间:2026-09-17。网络协议域·传输层。窗口既受接收方 rwnd 限制,也受网络 cwnd 限制,本篇讲接收方流量控制,网络侧见 TCP 拥塞控制。
TCP 用滑动窗口(sliding window)同时实现两件事:批量传输(不必发一个等一个 ACK)和流量控制(发送速率不超过接收方处理能力)。接收方在每个 ACK 里通告一个窗口大小 rwnd(receive window),告诉发送方"不用等确认,最多还能发多少字节"。
一、滑动窗口怎么工作
把发送缓冲区分成四段:
已确认 | 已发未确认(在途) | 允许发送未发 | 不允许(等窗口右移)
|<---- 发送窗口 swnd = min(cwnd, rwnd) ---->|- 收到累计 ACK,窗口左边界向右滑(被确认的移出窗口);
- 对端通告的 rwnd 决定右边界,接收方有余量就把窗口开大,应用读得慢就关小;
- 窗口内的数据可连续发出而无需逐个等待,大幅提高吞吐(这是停等协议到流水线的跨越)。
接收方同理维护接收窗口,按序字节放入、上层读走后前移,并在 ACK 中带上当前可用 rwnd。
二、rwnd 与 cwnd:两个窗口取小
实际发送窗口由两个独立限制的最小值决定:
swnd = min(rwnd, cwnd)| 窗口 | 由谁决定 | 保护谁 |
|---|---|---|
rwnd(接收窗口) | 接收方应用读取速度/接收缓冲 | 不把接收方撑爆(流量控制,本篇) |
cwnd(拥塞窗口) | 发送方根据网络拥塞动态调整 | 不把网络/中间路由器撑爆(拥塞控制) |
慢、卡可能是任一方:抓包看到
Win=0是接收方流控问题(应用没读),看到重传/RTT 增大、cwnd 被砍是网络拥塞。两者机制正交,见拥塞控制。
三、零窗口(Zero Window)与持续计时器
当接收方应用完全来不及读、接收缓冲填满,会通告 rwnd=0,发送方必须暂停发送(但连接还在):
接收方: ACK ..., Win=0 # 我满了,别发数据
发送方: 暂停;启动 persist timer(持续计时器)
发送方: 周期性发 Window Probe(只带 1 字节,可穿透零窗口)
接收方: 应用读走数据后 ACK, Win=非0 # 恢复- 持续探测防止"窗口更新 ACK 丢失导致永久死锁"(探测本身要确认)。
- 现象:某连接吞吐突然归零、抓包满是 Zero Window / ZeroWindowProbe——排查接收端应用为何不
read(阻塞、GC 停顿、处理太慢)。Linux 接收缓冲可经tcp_rmem、应用SO_RCVBUF调整(有 BQL/自动调节时内核会自适应)。
四、窗口扩大选项 WScale
TCP 头部窗口字段只有 16 bit,最大 65535 字节,在高 BDP(带宽×延迟)链路(如万兆×几十 ms)远远不够填满管道。三次握手时协商 Window Scale 选项,把窗口左移若干 bit:
实际窗口 = header_window << shift
shift=7 时 65535 可表示约 8MB长肥管道(长延迟高带宽)要跑满吞吐,需要足够大的窗口(窗口 ≥ 带宽×RTT)和 WScale、配合足够的收发缓冲。抓包握手里能看到
wscale选项;若一端关闭则整体不能缩放。BDP 与缓冲调优是高并发内核调参的一部分。
五、糊涂窗口综合征(SWS)
当接收方每次只腾出一点点缓冲就通告一个很小的窗口、发送方又急着发很小的段,会产生大量满载荷不足的小包,效率低下,称 silly window syndrome。两端各有对策:
- 接收端:窗口没增加到足够(如一个 MSS 或缓冲的一半)前,先不通告非零窗口(继续报 0)。
- 发送端:Nagle(见前篇)攒够一个 MSS 或窗口足够大再发。
这也解释了为什么"零窗口后恢复"不是立刻发 1 字节,而是等窗口健康。
六、观测
ss -ti '( dport = :443 )' # rtt、cwnd、bytes_sent,结合抓包看窗口
tcpdump -nn -i any 'tcp[tcpflags] & tcp-ack != 0' | head
# Wireshark 中看 tcp.analysis.zero_window、window_size(含 scale 计算后的值)
sysctl net.ipv4.tcp_rmem net.core.rmem_max相关阅读
总结
滑动窗口让发送方在 swnd=min(cwnd,rwnd) 内连续批量发送并随累计 ACK 滑动:rwnd 做接收方流量控制(应用读得慢会报 Zero Window 让对端暂停,用 persist 探测防死锁),cwnd 管网络拥塞;WScale 选项把 16bit 窗口扩大以填满高 BDP 管道,接收端延迟通告小窗 + 发送端 Nagle 共同避免糊涂窗口小包。