流控制是「接收方管发送方」:我说还能收多少,你就不能超过。 窗口缩到 0 时发送方必须停,但要靠「窗口探测」防止双方死等。
6.4.6 讲的窗口是「发送方能发多少」,这一节讲「接收方能收多少」。 6.4.9 的拥塞控制是「网络能承受多少」,这三者经常被混为一谈。
一、三个窗口要分清
这是最容易混的地方,先说清三个「窗口」各管什么:
| 名字 | 谁决定 | 管什么 |
|---|---|---|
| 发送窗口 | TCP 自己算 | 受下面两个限制 |
| 接收窗口 rwnd | 接收方通告 | 应用读得有多快 |
| 拥塞窗口 cwnd | 网络状况推测 | 链路能承受多少 |
发送方实际能发多少,取两者的较小值: 「接收方说能收 100,但我觉得网络只能走 30」,那就只发 30。 两个限制的目的完全不同:rwnd 防「把接收方淹了」,cwnd 防「把网络淹了」。 「rwnd 防淹接收方」这件事,在慢速设备上特别真实: 比如一个老式打印机、一个写盘很慢的服务器,它读应用的速度跟不上网络,缓冲区就会满。 这时候它通告的 rwnd 会一路缩小,最后变成 0。
二、窗口缩到 0 之后
rwnd 变成 0 时,发送方必须停下:这不是「网络断了」,是「对方说你先别发」。 但停下来之后有个风险:「窗口更新」那个通知本身是个包,它可能丢。 如果丢了,接收方以为「我已经告诉他可以了」,发送方以为「他还没准备好」,双方就死等。 所以有个兜底机制叫「窗口探测」: 发送方定期发一个「很小的包」试探,接收方收到必须回一个「现在窗口是多少」。 这等于「用轮询弥补通知可能丢失」。 这个设计思想很通用:「靠通知」的机制,永远要配一个「靠轮询」的兜底, 否则「通知丢失」会变成「永久卡死」。 分布式系统里的心跳、租约,用的是同一个思路。
三、窗口和「慢」的关系
很多人把「下载卡住」直接归因为「带宽不够」,但流控制造成的卡顿看起来是一样的: 连接没断、包也没丢,就是不动。 区分方法:看「接收窗口」是不是在缩小。 如果 rwnd 一路降到 0,就是「接收方读得太慢」——常见于「写磁盘跟不上」「单线程处理太慢」。 如果 rwnd 正常而速度上不去,那才可能是带宽或拥塞控制的问题。 所以「服务端慢」有时候不是网络的锅:应用读得慢,网络只好等着。 这也是「加带宽没用」的典型场景——瓶颈在接收方的应用层。
四、动手 Demo:窗口缩到零,发送方就得停
模拟接收窗口从 4000 一路缩到 0,看每轮能发多少。 第五轮开始窗口是 0,只能停。
rwnd, sent = 4000, 0
for step in range(1, 6):
can = min(rwnd, 1460)
if rwnd == 0:
print('%d rwnd=0 停 已发 %d' % (step, sent))
else:
sent += can
print('%d rwnd=%d 发 %d 已发 %d' % (step, rwnd, can, sent))
rwnd = max(0, rwnd - 1000)
实跑输出:
--------------------------------------------------------------------
窗口缩到零,发送方就得停
--------------------------------------------------------------------
**流控制是「接收方管发送方」**:**接收方说「我还能收多少」,发送方就不能超过这个数。**
轮次 接收窗口 rwnd 本轮可发 累计已发
-------------------------------------------------
1 4000 1460 1460
2 3000 1460 2920
3 2000 1460 4380
4 1000 1000 5380
5 0 停 5380
**第五行窗口变成 0,发送方就停了**:**这不是「网络断」,是「对方说你先别发」。**
**应用读得慢(比如磁盘写不过来),缓冲区就会满**,**于是窗口一路缩到 0。**
**所以「下载卡住但连接没断」很可能是「本机写盘跟不上」。**
**窗口为 0 之后还有个小机制**:**发送方会定期发「窗口探测」包**,
**问一句「现在能收了吗」**——**因为「窗口更新」那个通知本身可能丢**,**不发探测就会双方死等。**
**这种「靠通知」的设计,永远要配一个「靠轮询」的兜底。**
注意「本轮可发」一直是 1460:因为窗口还够发一整个段。 所以窗口缩小的过程是「先缩小到只能发一段,再缩到 0」。 如果窗口是 1000(比一段小):发送方只能发 1000 字节的「小段」, 效率立刻下降——这也是「窗口太小时性能崩掉」的原因。 所以实际实现里会避免让窗口小到「一段装不下」。 最后一个观察:这张表里没有「丢包」,速度还是降下来了。 所以「变慢」的原因有很多种,不能一看到慢就怀疑网络——先看是「丢包了」还是「对方读得慢」。
30 秒实验
- 想一下:rwnd 和 cwnd 各防什么?
- 想一下:窗口更新丢失会怎样?靠什么兜底?
- 想一下:为什么「窗口比一段还小」会让性能崩掉?
常见误区
- 以为「窗口就是带宽」。 窗口是「在路上的数据量」,带宽是链路能力,两者不同。
- 以为「rwnd 和 cwnd 是一回事」。 一个防淹接收方,一个防淹网络。
- 以为「速度慢就是带宽不够」。 流控制也会让速度降,看 rwnd 才能区分。
自测三问(点开对答案)
1. rwnd 和 cwnd 分别防什么? —— **rwnd 防把接收方淹没,cwnd 防把网络淹没**,实际发送取两者的较小值。 2. 窗口为 0 之后靠什么恢复? —— **靠窗口探测**:发送方定期发小包试探,接收方必须回当前窗口值。 3. 怎么判断「慢」是流控制造成的? —— **看接收窗口是不是一路缩小到 0**,是的话就是接收方读得慢。下一节讲「网络能承受多少」——拥塞控制。
- ☐ (本节完,下一节 6.4.9 拥塞控制)