累积确认的代价是「丢一段就卡住」:后面到的包只能先存着。 三次重复确认会触发快速重传,不用等超时,快一个数量级。
6.4.6 讲了窗口怎么提速,这一节讲「有了窗口之后,丢包怎么处理」。 6.4.3 的重传定时器是「慢路径」,这一节讲的是「快路径」。
一、丢一段会卡住多少
因为 ack 是累积的,它只能报「连续收到了多少」。 中间缺一段,后面的段虽然到了,ack 也不能跳过去——所以发送方看到的 ack 就停在缺口前面。 注意「卡住」不等于「丢了」:后面那些段其实已经安全到达接收方的缓冲区里了, 只是「不能交给应用」,因为要保证顺序。 所以「网络丢一个包」和「数据丢失」是两件事—— TCP 保证的是「最终按序交给应用」,中间允许乱序和等待。
二、快速重传
发送方怎么知道「是丢了,而不是网络慢」:看「重复确认」的次数。 收到一次重复 ack,可能只是乱序;连续收到三次,就说明「后面的包确实到了,只缺那一个」—— 于是立刻重传,不等超时。 为什么是三次不是两次:因为链路上本来就可能乱序, 一两次乱序是正常的,三次连续乱序的概率很低。 这个阈值是「误判」和「反应速度」之间的折中。 重传之后有个「幸福的意外」: 因为后面的段都还在缓冲区里,所以补上缺口之后,ack 会直接跳到最新位置—— 一次补齐,不需要逐个重传。
三、选择确认 SACK
累积确认还有个浪费:如果丢了两个不连续的段,发送方只知道「第一个缺口」, 补上之后才发现「还有一个」,于是又要等一轮。 SACK(选择确认)就是补这个的:接收方在确认里附带「我还收到了哪些区间」, 于是发送方一次就知道「该补哪几个」,不用一轮一轮试。 它靠在 TCP 首部的选项里加字段实现, 所以「首部长度可变的那个设计」在这里派上了用场—— 20 字节是基本头,选项区可以再加最多 40 字节。
四、动手 Demo:丢一段会卡住多少
模拟发五个段、第三个丢了,看每个段的到达情况和回过来的 ack。 注意第四、五个段到了,ack 还是 3。
segs, lost = [1, 2, 3, 4, 5], 3
expect = 1
for s in segs:
if s == lost:
print('段%d 丢 ack=%d(重复)' % (s, expect))
continue
if s == expect:
expect = s + 1
while expect in [x for x in segs if x != lost]:
expect += 1
print('段%d 到 ack=%d' % (s, expect))
实跑输出:
--------------------------------------------------------------------
丢一段会卡住多少
--------------------------------------------------------------------
**累积确认的代价就在这里**:**ack 只报「连续收到了多少」,中间缺一段就停在那儿。**
段号 到达情况 回过来的 ack
-----------------------------------
1 收 3
2 收 3
3 丢 3(重复)
4 收 3
5 收 3
**第 3 段丢了之后,第 4、5 段虽然到了,ack 还是 3**:**因为接收方不能「跳过缺口」确认。**
**这时的数据并没有丢,只是「卡在接收缓冲区里等着」。**
**所以「网络丢一个包」和「数据丢失」是两件事**——
**TCP 保证的是「最终按序交给应用」,中间允许乱序和等待。**
**发送方看到三次重复的 ack=3,就立刻重传第 3 段**,**这叫「快速重传」**,比等超时快一个数量级。
**而重传之后,ack 会直接跳到 6**——**因为 4、5 早已在缓冲区里,一次就补齐了。**
第 3 段丢了之后,第 4、5 段的 ack 还是 3:因为接收方不能跳过缺口确认。 而这三个重复的 3,就是「快速重传」的触发信号。 补上第 3 段之后,ack 直接跳到 6:因为 4、5 早就在缓冲区里了。 所以「丢一个包」的代价是「多一个往返」,不是「重传后面全部」—— 这一点是累积确认的「补偿效应」。 如果丢的是两个不连续的段:第一次补齐后 ack 只前进到第二个缺口, 于是又要一轮——这就是 SACK 要解决的问题。 有 SACK 的话,接收方一次就把「两个缺口」都报出来。
30 秒实验
- 想一下:为什么需要「三次」重复确认而不是一次?
- 想一下:补上缺口后 ack 为什么会「跳跃」?
- 想一下:SACK 解决了累积确认的什么浪费?
常见误区
- 以为「丢包就等于数据丢失」。 数据还在缓冲区里,只是暂时不能交给应用。
- 以为「重复 ack 是故障」。 它是「缺口信号」,三次重复就触发快速重传。
- 以为「重传要把后面的都重发」。 不用,补上缺口后一次就补齐了。
自测三问(点开对答案)
1. 累积确认的代价是什么? —— **丢一段就卡住**:后面到的包不能确认,只能等缺口补上。 2. 快速重传的触发条件是什么? —— **收到三次重复确认**,说明缺口之后的数据都到了,只缺那一个。 3. SACK 解决什么问题? —— **同时丢多个不连续的段时**,接收方一次把「收到哪些区间」都报出来,省掉一轮一轮试。下一节讲「接收方收不下的时候怎么办」。
- ☐ (本节完,下一节 6.4.8 流控制)