TCP 流量控制:滑动窗口如何防止接收方被打爆
围绕TCP 流量控制章节,讲清接收窗口、滑动窗口、ACK Window 字段、零窗口、窗口更新以及它和拥塞控制的区别。
相关工具
流量控制先保护接收方
资料 对 TCP 流量控制的解释很朴素:流量控制就是让发送速率不要过快,让接收方来得及接收。换句话说,发送方不能只看自己网速快、CPU 空,就一股脑把数据塞过去。接收方的应用程序可能处理慢,接收缓冲区可能快满了,继续发送就会造成缓存溢出或大量无效重传。
TCP 用滑动窗口机制来做这件事。接收方在 TCP 报文里告诉发送方:我现在还能接收多少字节。发送方根据这个窗口大小控制未确认数据量,窗口大就多发一些,窗口小就少发一些,窗口为 0 就暂停发送新的业务数据。
这里的重点是“接收方能力”。流量控制不是为了判断网络堵不堵,而是为了判断接收端吃不吃得下。网络很通畅,但接收方应用不读数据,接收缓冲区照样会满;这时发送方也必须停下来。
窗口字段告诉发送方还能发多少
已有内容提到,在 TCP 通信中,每个 TCP 报文段都包含窗口字段,该字段指示发送方可以发送多少字节的数据而不等待确认。这个窗口不是固定值,会随着接收方缓冲区的可用空间动态变化。
接收方维护接收缓冲区。数据到达后先进入缓冲区,应用程序再从缓冲区读取。如果应用读得快,可用空间就多;如果应用读得慢,未读取数据会堆在缓冲区里,可用空间就少。接收方会通过 ACK 报文里的 Window 字段,把当前可接收窗口告诉发送方。
发送方看到这个窗口后,会调整自己的发送节奏。窗口变大,说明接收方腾出空间,可以继续发送更多数据;窗口变小,说明接收方压力上来了,发送方要收着点。这个动态调整,就是 TCP 流量控制的基本动作。

接收方通过 ACK 的 Window 字段通告可接收窗口。发送方根据接收窗口控制可发送数据量,避免超过接收缓冲区能力。
发送窗口里有四类数据
理解滑动窗口,可以把发送缓冲区分成几段。第一段是已经发送并收到确认的数据,这些数据可以从缓冲区清掉。第二段是已经发送但还没收到确认的数据,它们还要留着,万一丢失需要重传。
第三段是当前允许发送的数据,也就是窗口里还没有发出去的部分。发送方可以继续把这些数据发出去。第四段是暂时不能发送的数据,因为它已经超出了接收方通告的窗口范围。哪怕应用层已经把数据写进发送缓冲区,TCP 也不能随便越界发送。
当 ACK 回来时,确认号向前推进,窗口也可能更新,发送窗口就像尺子一样向前滑。已经确认的数据退出窗口,新的一段数据进入可发送范围。这个过程让发送方既能连续发送,又不会超过接收方承受能力。
接收窗口取决于缓冲区剩余空间
接收方的窗口大小,本质上来自接收缓冲区的剩余可用空间。假设接收缓冲区总容量是 64 KB,已经收到但应用还没读取的数据占了 40 KB,那么可用空间大约就是 24 KB。接收方就可以在 ACK 里通告一个相应大小的接收窗口。
如果应用程序一直不读数据,缓冲区里的未读数据越来越多,可用空间就会缩小。接收方通告的窗口也会变小。发送方收到后,能继续发送的数据量减少,甚至只能等已发送数据被确认后再看有没有新窗口。
如果应用程序开始读取数据,缓冲区释放出空间,接收方就会通告更大的窗口。发送方收到窗口更新后,继续发送。这个过程不需要应用层自己告诉对方“我忙不过来了”,TCP 已经在传输层用窗口字段表达了这个状态。
零窗口:接收方真的没空间了
当接收方缓冲区没有可用空间时,它会通告 `Window=0`。这就是零窗口。发送方看到零窗口后,不能继续发送新的业务数据,否则接收方也放不下。此时发送方会进入等待状态,保留已经发送但还没确认的数据。
零窗口不是异常报错,而是一种流量控制状态。它表达的是:接收方现在忙不过来,请先停一下。真正要关注的是零窗口持续多久、出现频率多高。如果只是短暂出现,说明接收方应用偶尔慢一点;如果长期出现,就要查接收端应用是否阻塞、消费线程是否卡住、缓冲区是否太小。
等接收方应用读取数据、释放缓冲区后,接收方会发送窗口更新 ACK,告诉发送方现在又有空间了。发送方收到新的窗口大小,就可以恢复发送。

接收方缓冲区满时通告 Window=0,发送方暂停发送;应用读取数据释放空间后,接收方通告新的窗口,发送方恢复发送。
流量控制和拥塞控制别混
资料 在同一页接着讲 TCP 拥塞控制,这两个概念很容易混。流量控制看的是接收方能力,核心变量可以理解为接收窗口 `rwnd`。接收方缓冲区还有多少空间,就通告多大的窗口,目的是防止发送方压垮接收端。
拥塞控制看的是网络能力,核心变量是拥塞窗口 `cwnd`。它关心链路中间是不是拥塞、丢包是不是变多、路由和队列是不是承受不了。慢启动、拥塞避免、快速重传、快速恢复,都是围绕网络拥塞程度调节发送速率。
真实发送时,发送方不能只看其中一个。接收方说还能接收 64 KB,但网络很拥塞,发送方也不能猛发;网络很空,但接收方只剩 4 KB 缓冲区,也不能超过接收方窗口。实际可发送窗口通常受 `min(rwnd, cwnd)` 限制。
ACK 不只是确认,也带窗口信息
前面讲可靠传输时,我们会把 ACK 理解成“我收到了”。在流量控制里,ACK 还有另一个角色:它会携带接收方当前窗口大小。接收方通过 ACK 告诉发送方确认号,也告诉发送方还能接多少。
这就是为什么 ACK 和窗口机制联系很紧。确认号让发送方知道哪些数据已经安全到达,可以从发送缓冲区释放;窗口字段让发送方知道还能有多少未确认数据在路上。一个管进度,一个管容量。
抓包时如果只看 Seq/Ack,不看 Window,就容易漏掉流量控制问题。比如应用慢导致窗口越来越小,最终出现零窗口,抓包里会看到 Window Size 逐步下降。这不是丢包,也不是带宽不够,而是接收端处理能力跟不上。
工程排查:看到慢不一定是网络慢
如果 TCP 连接吞吐突然下降,很多人第一反应是网络慢。流量控制提醒我们,还要看接收端是不是慢。比如服务端把响应发给客户端,客户端应用没有及时读取,客户端接收窗口变小,服务端发送自然会被压住。
排查时可以看几类指标:接收端应用是否阻塞,读取线程是否忙,Socket 接收缓冲区是否长期接近满,抓包里 Window Size 是否持续偏小或为 0,发送端是否大量等待窗口更新。只要窗口被接收方压小,发送方就算有带宽也发不出去。
这类问题在日志采集、文件传输、代理转发、长连接服务里都可能出现。表现可能是吞吐低、延迟高、连接看似不断但数据推进很慢。它不一定是 TCP 的错,往往是接收端应用处理链路出了瓶颈。
面试里怎么讲
如果被问 TCP 流量控制,可以先说目的:防止发送方发送太快,超过接收方接收缓冲区和应用处理能力。TCP 通过滑动窗口实现流量控制,接收方在 ACK 报文的 Window 字段中通告自己的可接收窗口。
然后讲过程:发送方根据接收窗口控制未确认数据量;接收方缓冲区剩余空间变小,通告窗口变小,发送方降低发送量;应用读取数据释放空间后,接收方通告窗口变大,发送方继续发送。如果窗口为 0,发送方暂停发送新的业务数据,等待窗口更新。
最后讲区别:流量控制保护接收方,看 `rwnd`;拥塞控制保护网络,看 `cwnd`。实际发送窗口受两者共同限制,不能超过接收方能力,也不能忽视网络拥塞程度。
常见问题
TCP 流量控制解决什么问题?
它防止发送方发送太快,导致接收方缓冲区溢出或处理不过来。接收方通过 ACK 的 Window 字段通告可接收窗口,发送方据此调整发送量。
零窗口是不是连接坏了?
不一定。零窗口表示接收方当前没有可用接收缓冲区,发送方需要暂停发送。短暂零窗口是正常流量控制,长期零窗口才需要排查接收端应用是否阻塞。
流量控制和拥塞控制有什么区别?
流量控制看接收方能力,避免压垮接收端;拥塞控制看网络拥塞程度,避免压垮网络。实际发送窗口通常受接收窗口和拥塞窗口共同限制。