ECN 把「丢包」这个迟到的信号,换成了「打个标记」:路由器先说一声,主机自己减速。 技术上正确、工程上难推:中间设备、两端、运营商策略,三关都得过。
6.4.9 讲了 TCP 怎么靠丢包发现拥塞,这一节讲「不丢包也能发现」。 5.8.3 的 QoS 管「谁先走」,ECN 管「走不动的时候通知一声」。
一、用丢包当信号,太晚了
TCP 的拥塞控制靠「丢包」判断「路上堵了」, 而丢包意味着「数据已经没了」——这是「事后才知道」,而且代价是一个重传。 更麻烦的是「丢包」这个信号很含糊:它分不清「拥塞丢了」和「链路误码丢了」, 而这两种情况的正确反应完全不同——前者该减速,后者减速反而更糟(无线链路上这个问题最突出)。 ECN 的想法很朴素:路由器队列快满的时候,别等满了再丢,而是「在包头上做个记号」。 收方看到记号,通过确认告诉发方,发方减速。 整个过程一个包都没丢,所以一次重传都不需要。
二、两个位,四种状态
IP 首部里用两位表示 ECN 状态:
| 两位 | 含义 | 说明 |
|---|---|---|
| 00 | 不支持 ECN | 照老规矩丢包 |
| 01 | ECT(1) | 我愿意接收标记 |
| 10 | ECT(0) | 同上,另一个编号 |
| 11 | CE | 路上已经拥塞了 |
第二、三行是最容易被问的地方:「愿意接收标记」为什么要两个编号? 答案是「用来检测路径上有人偷偷改标记」—— 发送方交替发 ECT(0) 和 ECT(1),收到的记号对不上就说明中间有人动过手。 第四行才是真正的信号:CE(Congestion Experienced)由路由器打上, 而且「打了就不再丢」——这是 ECN 最有价值的地方。
三、完整的回路
ECN 不是「一个设备的事」,是一条四步的回路:
| 步 | 谁做 | 做什么 |
|---|---|---|
| 1 | 发送方 | 在包上标 ECT |
| 2 | 路由器 | 队列快满时标 CE,不丢包 |
| 3 | 接收方 | 在 ACK 里置 ECE 位回传 |
| 4 | 发送方 | 减窗,并在下一个包置 CWR |
第三、四步是「TCP 层的事」:IP 头里只有两个位,没法「回传」, 所以必须借 TCP 首部的 ECE 和 CWR 两个标志。 这也是「ECN 必须两端都支持」的原因——一端不认,回路就断在中间。 第四步那个 CWR(拥塞窗口已减小)看着多余,其实是必需的: 它告诉接收方「我已经减速了,你别再一直回显」, 否则接收方会持续回显,发送方就会一直减窗,直接把窗口减到零。
四、为什么推不动
三关,一关不过就退回丢包:
| 关卡 | 问题 | 后果 |
|---|---|---|
| 中间设备 | 老设备把带标记的包当异常丢掉 | 直接不通 |
| 两端主机 | 一端不支持就只能按丢包处理 | 等于没开 |
| 运营商策略 | 有些网络会清掉这两位 | 信号被抹掉 |
第一行是最坑的:本来是想「少丢包」,结果因为标了记而被丢,这叫「开启 ECN 之后更慢」, 所以很多系统默认只对「内网」开,对公网关。 第三行是「策略问题」不是「技术问题」:有些运营商出于「统一行为」的考虑会清位, 而清位之后,发送方看到的就是「一路畅通」——拥塞信号被中间人吃掉了。 所以 ECN 是「技术上正确,工程上难推」的典型:它要求「路径上每一跳都守规矩」, 而互联网恰恰是一堆「各自为政」的网络拼起来的。
五、动手 Demo:ECN,让路由器「说一声」而不是「丢一个」
算一下 1% 丢包下,1 万个包里有多少要重传、各多等几个往返。 再对照 ECN 的四种状态看「标记」和「丢弃」的差别。
total, loss = 10000, 0.01
lost = int(total * loss)
rtt_ms = 50
print('丢包数', lost, '串行重传合计', lost * rtt_ms, 'ms')
print('ECN 只减速,不重传:0 个包需要重发')
实跑输出:
--------------------------------------------------------------------
ECN,让路由器「说一声」而不是「丢一个」
--------------------------------------------------------------------
**传统的拥塞信号是「丢包」**:**路由器队列满了就丢,TCP 看到丢包才减速。**
**ECN 的想法是「先打个标记,别丢」。**
**首部里用两位表示四种状态**:
两位 含义 说明
---------------------------------------------------
**00** **不支持 ECN** **照老规矩丢包**
**01** **ECT(1)** **我愿意接收标记**
**10** **ECT(0)** **同上,另一个编号**
**11** **CE** **路上已经拥塞了**
**关键在于「标记」和「丢弃」的区别**:**标记的包照样送到,只是告诉接收方「该让发送方慢一点」。**
**接收方通过 ACK 把这件事回传,发送方减窗。**
**整个过程没有丢包,所以没有重传。**
**算一下省下的代价**:
**假设 1% 的丢包率,1 万个包里有 100 个要重传。**
**每个重传都要一个往返,按 50 毫秒算就是 5000 毫秒的额外延迟**——
**而 ECN 只是「少发几个包」,没有重传。**
**但 ECN 的部署一直不快**,原因很现实:
**一是「中间设备要支持」**(老旧设备会把带标记的包当异常丢掉);
**二是「两端都要支持」**(一端不支持就退回丢包);
**三是「有些运营商会清掉这两个位」**(出于策略或设备限制)。**
**所以 ECN 是「技术上正确,工程上难推」的典型。**
「5000 毫秒」这个数是「串行合计」的口径,要说清楚: 真实的重传是并行的,所以不会真的多等 5 秒; 但「至少多等一个往返」是铁定的——因为发现丢包本身就要等一个超时或者三个重复确认。 ECN 省掉的正是「发现」这一步:路由器在队列还没满的时候就告诉你了, 所以「减速」发生在「丢包」之前。 这就是「主动队列管理」(AQM)的思路—— RED、CoDel 这些算法都是「早一点提醒」,ECN 只是「提醒的方式」。 最后一个判断:ECN 和数据中心的关系比和互联网近得多: 因为数据中心里「两端和中间设备都是自己的」,三关一次全过。 所以现在大规模用 ECN 的地方,基本都在机房内部。
30 秒实验
- 算一下:0.1% 丢包、10 万个包,有多少个要重传?
- 想一下:为什么 ECT 要有两个编号?
- 想一下:为什么「开了 ECN 反而更慢」?
常见误区
- 以为「ECN 是一种拥塞控制算法」。 它是一种「通知机制」,减速还是 TCP 自己在做。
- 以为「标了 CE 的包会被丢」。 恰恰相反,标记的包照样送到,只是告诉对方该慢一点。
- 以为「ECN 只要路由器支持就行」。 两端主机也要支持,否则回路断在中间。
自测三问(点开对答案)
1. ECN 相比丢包信号,好在哪? —— **它在丢包之前就通知**,所以不用重传,也不用等超时。 2. CE 是什么意思? —— **Congestion Experienced**,由路由器打上,表示「路上已经拥塞了」,但包不丢。 3. 为什么 ECN 在公网上推不动? —— **要路径上每一跳都守规矩**:老设备可能丢标记包,运营商可能清位。下一节是第五章最后一节:让「换网络不断连接」的 Mobile IP。
- ☐ (本节完,下一节 5.8.5 Mobile IP)