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

Chapter 3-2:可靠数据传输 rdt 原理学习指南
agsdChapter 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 | make_pkt(data) 负责构造 packet |
所以 udt_send() 是“发送动作”,不是“封装动作”。
3. FSM:有限状态机
rdt 协议通常用 FSM,finite state machine,有限状态机 描述。
一个 FSM 关注三件事:
- 当前处于什么状态;
- 发生了什么事件;
- 执行什么动作,并转移到哪个状态。
例如发送方可能处于:
- 等待上层调用;
- 等待 ACK;
- 等待 ACK0;
- 等待 ACK1。
接收方可能处于:
- 等待 pkt0;
- 等待 pkt1。
FSM 的关键不是看图形,而是看每条边:
在某个状态下,遇到某个事件,协议应该做什么。
flowchart TD
A[当前状态] --> B[事件发生]
B --> C[执行动作]
C --> D[进入下一状态]
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 | 发送方发送 packet |
如果接收方收到损坏分组:
1 | 发送方发送 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 | ACK / NAK 损坏 |
6.2 新机制:sequence number
rdt2.1 给数据分组加上序号:
1 | pkt0 = [seq=0, data, checksum] |
因为 rdt2.1 仍然是 stop-and-wait 协议:
一次只允许一个未确认分组在路上。
所以只需要两个序号:
1 | 0, 1, 0, 1, 0, 1 ... |
6.3 ACK 损坏时发生什么?
假设发送方发送 pkt0:
1 | 发送方 → 接收方:pkt0 |
但 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 | 发送 pkt0 |
8.3 ACK 丢失时怎么办?
假设:
1 | 发送方发送 pkt0 |
发送方等不到 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、超时重传 |
可以把演进逻辑记成:
flowchart TD
A[rdt1.0: 假设信道可靠] --> B[rdt2.0: 数据可能比特出错]
B --> C[加入 checksum + ACK/NAK]
C --> D[发现 ACK/NAK 也可能损坏]
D --> E[rdt2.1: 加 sequence number]
E --> F[rdt2.2: 用重复 ACK 替代 NAK]
F --> G[发现分组或 ACK 可能丢失]
G --> H[rdt3.0: 加 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 | 发送一个分组 |
这意味着:
一次只有一个未确认分组在路上。
如果 RTT 很大,或者链路带宽很高,发送方大部分时间都在等 ACK,链路空闲时间很长,利用率很低。
所以 rdt3.0 的问题不是“不可靠”,而是:
太保守,无法充分利用链路容量。
❗ 本节核心结论:
rdt3.0 可靠,但 stop-and-wait 使它一次只能发送一个未确认分组,因此在高带宽或高 RTT 场景下链路利用率很低。
12. 最容易混淆的点
12.1 checksum 能不能处理丢包?
不能。
checksum 只能检查“收到的 packet 有没有坏”。
如果 packet 根本没到,接收方没有任何东西可以检查。
丢包必须依靠 timeout 处理。
12.2 重传是不是一定避免重复?
不是。
重传反而可能制造重复。
例如 ACK 丢失时:
1 | 接收方已经收到数据并交付 |
真正避免重复交付的是:
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。
它们的策略是:
- 发送方遇到损坏反馈时,保守重传当前分组;
- 接收方用 sequence number 判断是否重复;
- 重复分组不再交付给上层,只重新发送确认。
所以解决的是:
ACK / NAK 损坏后,协议仍然能正确继续运行。
13. 本节一句话总复习
rdt 的演进可以概括为:
从假设信道完全可靠开始,每遇到一种新的不可靠情况,就增加一种机制:比特错误用 checksum 和 ACK/NAK;ACK/NAK 损坏用 sequence number 和重复检测;分组或 ACK 丢失用 timer 和超时重传。
14. 自测题:暂不附答案
为什么说 rdt 的目标不是“把 packet 发出去”,而是“让数据正确交付给上层一次”?
rdt_send()、udt_send()、rdt_rcv()、deliver_data()分别位于哪一侧?各自作用是什么?为什么课件说“只考虑单向数据传输”,但控制信息仍然是双向流动的?
rdt1.0 为什么不需要 ACK、NAK、checksum 和 timer?
rdt2.0 引入 checksum 后,为什么还需要 ACK 和 NAK?
rdt2.0 的致命问题是什么?为什么 ACK / NAK 出错会让发送方陷入不确定状态?
rdt2.1 为什么要引入 sequence number?它主要解决的是“比特出错”问题还是“重复分组”问题?
为什么在 stop-and-wait 协议中,只需要 0 和 1 两个序号就够?
rdt2.2 为什么可以不要 NAK?重复 ACK 在语义上相当于什么?
rdt3.0 中,timer 和 timeout 分别解决什么问题?
如果 ACK 丢失,发送方会怎么做?接收方如何避免重复交付数据?
如果 ACK 只是延迟而不是丢失,rdt3.0 为什么仍然能正确工作?它的代价是什么?
checksum、sequence number、ACK、timer 四个机制分别解决哪类不可靠问题?
为什么说 rdt3.0 是可靠的,但性能可能很差?
用一句话概括 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 的设计思想是:当发送方无法直接知道接收方状态时,就通过校验、确认、序号、重传和定时器,把“不确定的底层传输”转化为“上层可依赖的可靠传输”。








