Chapter 3-2:可靠数据传输 rdt 原理学习指南

Chapter 3-2:可靠数据传输 rdt 原理学习指南

这份课件虽然文件名是 Chapter3-2,但主体内容是 可靠数据传输 reliable data transfer, rdt,而不是目录意义上的“多路复用与解复用”。

本节的核心问题是:

底层信道不可靠,但上层应用希望看到可靠通信,传输层协议应该如何设计?


0. 本节核心主线

可靠数据传输不是简单地“把 packet 发出去”,而是要保证:

数据最终被正确地、按协议预期地交付给接收方上层。

底层信道可能出现的问题包括:

  • 分组比特出错;
  • 数据分组丢失;
  • ACK / NAK 控制报文损坏;
  • ACK 丢失;
  • 分组或 ACK 延迟到达;
  • 发送方无法直接知道接收方的真实状态。

所以 rdt 协议要用一套机制把不可靠信道包装成可靠服务。


❗ 本节核心结论:
rdt 的本质,是在不可靠信道上,通过 checksum、ACK、sequence number、timer 等机制,让上层看到一个可靠的数据传输抽象。

1. 可靠服务抽象与可靠服务实现

要先区分两个层次:

层次 含义
可靠服务抽象 上层看到的通信好像是可靠的
可靠服务实现 底层实际不可靠,靠协议机制实现可靠效果

上层应用希望的是:

  • 数据不出错;
  • 数据不丢失;
  • 数据不重复交付;
  • 数据最终能正确到达。

但底层网络,尤其是 IP 层,通常只提供 best-effort service,即尽力而为服务。它不保证可靠性。

因此,传输层需要在不可靠网络层之上构造可靠数据传输。

可以记成一句话:

底层并没有真的变可靠,只是 rdt 把不可靠性屏蔽了。


2. rdt 的四个接口函数

课件中常见四个接口:

接口 所在位置 作用
rdt_send(data) 发送方,上层调用 rdt 上层把 data 交给可靠传输协议
udt_send(packet) 发送方,rdt 调用底层信道 把 packet 交给不可靠信道发送
rdt_rcv(packet) 接收方,底层信道调用 rdt 接收方 rdt 收到 packet 后进行检查
deliver_data(data) 接收方,rdt 调用上层 确认数据正确后交付给应用层

这里有一个容易错的地方:

udt_send() 不负责打包。
真正负责构造 packet 的通常是类似 make_pkt() 的动作。

更准确地说:

1
2
make_pkt(data) 负责构造 packet
udt_send(packet) 负责把 packet 放入不可靠信道

所以 udt_send() 是“发送动作”,不是“封装动作”。


3. FSM:有限状态机

rdt 协议通常用 FSM,finite state machine,有限状态机 描述。

一个 FSM 关注三件事:

  1. 当前处于什么状态;
  2. 发生了什么事件;
  3. 执行什么动作,并转移到哪个状态。

例如发送方可能处于:

  • 等待上层调用;
  • 等待 ACK;
  • 等待 ACK0;
  • 等待 ACK1。

接收方可能处于:

  • 等待 pkt0;
  • 等待 pkt1。

FSM 的关键不是看图形,而是看每条边:

在某个状态下,遇到某个事件,协议应该做什么。


4. rdt1.0:可靠信道上的可靠传输

4.1 基本假设

rdt1.0 假设底层信道完全可靠:

  • 不会比特出错;
  • 不会丢包;
  • 不会乱序。

因此发送方只需要:

1
接收 data → 封装 packet → 发送

接收方只需要:

1
收到 packet → 提取 data → deliver_data()

4.2 为什么 rdt1.0 很简单?

因为它的底层假设太强了。
如果信道本身已经可靠,传输层几乎不用额外机制。

rdt1.0 的价值不是实用,而是作为后续协议演进的起点。


❗ 本节核心结论:
rdt1.0 说明:如果底层信道可靠,可靠传输协议几乎不需要复杂机制。

5. rdt2.0:处理比特差错

5.1 新问题

rdt2.0 放宽假设:

分组可能发生比特错误。

例如 packet 在传输过程中某些 bit 被翻转。

5.2 新机制

rdt2.0 引入:

机制 作用
checksum 检测 packet 是否发生比特错误
ACK 接收方告诉发送方:正确收到
NAK 接收方告诉发送方:收到但出错
retransmission 出错时重传当前 packet

5.3 工作过程

如果接收方收到正确分组:

1
2
3
4
5
发送方发送 packet
接收方 checksum 检查正确
接收方 deliver_data()
接收方发送 ACK
发送方收到 ACK,发送下一个 packet

如果接收方收到损坏分组:

1
2
3
4
5
发送方发送 packet
接收方 checksum 检查错误
接收方发送 NAK
发送方收到 NAK
发送方重传当前 packet

5.4 rdt2.0 的致命问题

rdt2.0 假设 ACK / NAK 本身不会出错。
但现实中 ACK / NAK 也是分组,也可能发生比特错误。

一旦 ACK / NAK 损坏,发送方就陷入不确定:

可能情况 发送方能否知道?
接收方正确收到数据,发了 ACK,但 ACK 损坏 不知道
接收方收到坏数据,发了 NAK,但 NAK 损坏 不知道

这时发送方不知道该重传还是不重传。


❗ 本节核心结论:
rdt2.0 解决了数据分组比特出错,但没有解决 ACK / NAK 自身损坏的问题。

6. rdt2.1:加入序号,处理重复分组

6.1 rdt2.1 要解决什么?

rdt2.1 不是直接“修好损坏的 ACK / NAK”。

它解决的是:

ACK / NAK 损坏后,发送方保守重传可能造成重复分组,接收方如何识别重复并避免重复交付。

逻辑链是:

1
2
3
4
5
6
7
8
9
ACK / NAK 损坏

发送方不知道接收方状态

发送方选择重传当前分组

重传可能导致接收方收到重复分组

需要 sequence number 区分新分组和重复分组

6.2 新机制:sequence number

rdt2.1 给数据分组加上序号:

1
2
pkt0 = [seq=0, data, checksum]
pkt1 = [seq=1, data, checksum]

因为 rdt2.1 仍然是 stop-and-wait 协议:

一次只允许一个未确认分组在路上。

所以只需要两个序号:

1
0, 1, 0, 1, 0, 1 ...

6.3 ACK 损坏时发生什么?

假设发送方发送 pkt0:

1
2
3
4
发送方 → 接收方:pkt0
接收方收到 pkt0,checksum 正确
接收方 deliver_data(pkt0)
接收方 → 发送方:ACK

但 ACK 损坏了。

发送方收到损坏的 ACK 后无法判断真实情况,于是重传 pkt0。

接收方再次收到 pkt0,但它已经收过 pkt0,现在期待的是 pkt1。于是接收方判断:

1
这个 pkt0 是重复分组

于是:

  • 不调用 deliver_data()
  • 丢弃重复数据;
  • 重新发送 ACK。

这就保证了:

可以允许发送方保守重传,但不能允许接收方重复交付。


❗ 本节核心结论:
rdt2.1 的 sequence number 不是为了检测比特错误,而是为了识别重复分组,避免重复交付。

7. rdt2.2:去掉 NAK,只保留带编号 ACK

7.1 rdt2.2 和 rdt2.1 的关系

rdt2.2 的功能和 rdt2.1 类似,但它做了一个重要简化:

不再使用 NAK,只使用带编号 ACK。

也就是说,接收方不再显式说“我没收到好”,而是反复确认“我最后正确收到的是谁”。

7.2 重复 ACK 如何替代 NAK?

假设发送方发 pkt1,但接收方没有正确收到 pkt1。

在 rdt2.1 中,接收方可能返回:

1
NAK

在 rdt2.2 中,接收方返回:

1
ACK0

ACK0 的语义是:

我最后正确收到的是 pkt0,我还没有正确收到 pkt1。

如果发送方正在等待 ACK1,却收到了 ACK0,就知道 pkt1 没有被正确接收,于是重传 pkt1。

所以重复 ACK 的含义不是简单粗暴地“请重传”,而是:

我最后正确收到的仍然是上一个分组,因此当前期待的分组还没收到好。

7.3 rdt2.1 与 rdt2.2 对比

对比项 rdt2.1 rdt2.2
使用 ACK
使用 NAK
ACK 是否带编号 可以强调,也可以不突出 必须带编号
出错时反馈 NAK 重复发送上一个正确 ACK
遇到损坏反馈 发送方重传当前分组 发送方重传当前分组
避免重复交付 sequence number sequence number

❗ 本节核心结论:
rdt2.2 用“重复 ACK 上一个正确分组”来表达“当前分组没有正确收到”,从而取消了 NAK。

8. rdt3.0:处理丢包

8.1 新问题

rdt2.x 主要处理的是:

  • 数据分组比特出错;
  • ACK / NAK 比特出错;
  • 重复分组。

但它还没有处理:

  • 数据分组丢失;
  • ACK 丢失。

如果分组或 ACK 直接丢失,发送方可能永远等不到反馈,协议会卡死。

8.2 新机制:timer / timeout

rdt3.0 引入:

机制 作用
timer 发送 packet 后开始计时
timeout 超过设定时间仍未收到正确 ACK,则重传

发送方逻辑:

1
2
3
4
5
6
7
8
9
10
11
发送 pkt0
启动 timer
等待 ACK0

如果收到 ACK0:
停止 timer
发送 pkt1

如果 timeout:
重传 pkt0
重新启动 timer

8.3 ACK 丢失时怎么办?

假设:

1
2
3
4
5
发送方发送 pkt0
接收方收到 pkt0
接收方 deliver_data(pkt0)
接收方发送 ACK0
ACK0 丢失

发送方等不到 ACK0,timer 超时,于是重传 pkt0。

接收方再次收到 pkt0,会发现它是重复分组:

  • 不调用 deliver_data()
  • 重新发送 ACK0。

所以即使 ACK 丢失,rdt3.0 仍然正确。

8.4 如果 ACK 只是延迟呢?

如果 ACK 没丢,只是来晚了,发送方可能提前 timeout,导致不必要重传。

这不会破坏正确性,因为接收方会用 sequence number 识别重复分组。

但它会带来效率损失:

  • 不必要重传;
  • 重复分组;
  • 重复 ACK;
  • 链路利用率下降。

❗ 本节核心结论:
timeout 不是证明“分组一定丢了”,而是发送方在无法确认状态时采取的保守恢复机制。

9. rdt 各版本演进总表

版本 信道假设 新问题 新机制
rdt1.0 完全可靠 直接发送、直接交付
rdt2.0 分组可能比特出错 如何发现并恢复错误 checksum、ACK、NAK、重传
rdt2.1 ACK / NAK 也可能出错 重传可能造成重复交付 sequence number、重复检测
rdt2.2 同 rdt2.1 能否取消 NAK 带编号 ACK、重复 ACK
rdt3.0 分组或 ACK 可能丢失 等不到 ACK 会死锁 timer、timeout、超时重传

可以把演进逻辑记成:


10. 四大机制分别解决什么问题

这是你自测题里最需要修正的一组对应关系。

机制 解决的问题
checksum 检测数据分组或控制报文是否发生比特错误
ACK / NAK 让接收方把“正确收到 / 收到但出错”的状态反馈给发送方
sequence number 区分新分组和重复分组,避免重复交付
timer / timeout 处理数据分组或 ACK 丢失导致发送方一直等待的问题

注意:

  • ACK 不是检错机制;
  • checksum 才是检错机制;
  • sequence number 不是检测比特错误,而是检测重复;
  • timer 不能确认真的丢包,只是在长时间无反馈时触发重传。

❗ 本节核心结论:
checksum 管“错没错”,ACK 管“对方怎么反馈”,sequence number 管“是不是重复”,timer 管“没反馈怎么办”。

11. 为什么 rdt3.0 可靠但性能差?

rdt3.0 已经能处理:

  • 比特错误;
  • 重复分组;
  • 数据分组丢失;
  • ACK 丢失;
  • ACK 延迟导致的重复重传。

所以它是可靠的。

但是它的性能可能很差,因为它是 stop-and-wait 协议。

stop-and-wait 的特点是:

1
2
3
4
5
发送一个分组

等待 ACK

收到 ACK 后才能发下一个分组

这意味着:

一次只有一个未确认分组在路上。

如果 RTT 很大,或者链路带宽很高,发送方大部分时间都在等 ACK,链路空闲时间很长,利用率很低。

所以 rdt3.0 的问题不是“不可靠”,而是:

太保守,无法充分利用链路容量。


❗ 本节核心结论:
rdt3.0 可靠,但 stop-and-wait 使它一次只能发送一个未确认分组,因此在高带宽或高 RTT 场景下链路利用率很低。

12. 最容易混淆的点

12.1 checksum 能不能处理丢包?

不能。

checksum 只能检查“收到的 packet 有没有坏”。
如果 packet 根本没到,接收方没有任何东西可以检查。

丢包必须依靠 timeout 处理。

12.2 重传是不是一定避免重复?

不是。

重传反而可能制造重复。

例如 ACK 丢失时:

1
2
3
4
接收方已经收到数据并交付
但发送方没收到 ACK
发送方 timeout 后重传
接收方再次收到同一个分组

真正避免重复交付的是:

1
sequence number + 接收方重复检测

12.3 ACK 是不是可靠的?

不是。

ACK 也是网络中的分组,也可能:

  • 损坏;
  • 丢失;
  • 延迟。

所以不能把 ACK 当成天然可靠的信号。

12.4 timeout 是否等于一定丢包?

不是。

timeout 只说明:

发送方在设定时间内没有等到正确 ACK。

可能是:

  • 数据分组丢了;
  • ACK 丢了;
  • ACK 延迟;
  • timer 设置太短。

12.5 rdt2.1 / rdt2.2 如何解决 ACK/NAK 损坏?

它们不是修复损坏的 ACK / NAK。

它们的策略是:

  1. 发送方遇到损坏反馈时,保守重传当前分组;
  2. 接收方用 sequence number 判断是否重复;
  3. 重复分组不再交付给上层,只重新发送确认。

所以解决的是:

ACK / NAK 损坏后,协议仍然能正确继续运行。


13. 本节一句话总复习

rdt 的演进可以概括为:

从假设信道完全可靠开始,每遇到一种新的不可靠情况,就增加一种机制:比特错误用 checksum 和 ACK/NAK;ACK/NAK 损坏用 sequence number 和重复检测;分组或 ACK 丢失用 timer 和超时重传。


14. 自测题:暂不附答案

  1. 为什么说 rdt 的目标不是“把 packet 发出去”,而是“让数据正确交付给上层一次”?

  2. rdt_send()udt_send()rdt_rcv()deliver_data() 分别位于哪一侧?各自作用是什么?

  3. 为什么课件说“只考虑单向数据传输”,但控制信息仍然是双向流动的?

  4. rdt1.0 为什么不需要 ACK、NAK、checksum 和 timer?

  5. rdt2.0 引入 checksum 后,为什么还需要 ACK 和 NAK?

  6. rdt2.0 的致命问题是什么?为什么 ACK / NAK 出错会让发送方陷入不确定状态?

  7. rdt2.1 为什么要引入 sequence number?它主要解决的是“比特出错”问题还是“重复分组”问题?

  8. 为什么在 stop-and-wait 协议中,只需要 0 和 1 两个序号就够?

  9. rdt2.2 为什么可以不要 NAK?重复 ACK 在语义上相当于什么?

  10. rdt3.0 中,timer 和 timeout 分别解决什么问题?

  11. 如果 ACK 丢失,发送方会怎么做?接收方如何避免重复交付数据?

  12. 如果 ACK 只是延迟而不是丢失,rdt3.0 为什么仍然能正确工作?它的代价是什么?

  13. checksum、sequence number、ACK、timer 四个机制分别解决哪类不可靠问题?

  14. 为什么说 rdt3.0 是可靠的,但性能可能很差?

  15. 用一句话概括 rdt1.0 → rdt2.0 → rdt2.1 → rdt2.2 → rdt3.0 的演进逻辑。


最终总结

Chapter 3-2 的核心不是背 rdt1.0、rdt2.0、rdt2.1、rdt2.2、rdt3.0 的名字,而是理解它们为什么一步步变复杂。

每一个版本都对应一种新的“不可靠性”:

  • rdt1.0:假设没有不可靠;
  • rdt2.0:处理数据比特错误;
  • rdt2.1:处理 ACK / NAK 损坏引发的重复问题;
  • rdt2.2:用带编号 ACK 替代 NAK;
  • rdt3.0:处理分组或 ACK 丢失。

❗ 最终核心结论:
rdt 的设计思想是:当发送方无法直接知道接收方状态时,就通过校验、确认、序号、重传和定时器,把“不确定的底层传输”转化为“上层可依赖的可靠传输”。