别再死记硬背了!用Python模拟RDT协议(rdt1.0到3.0)的FSM状态机,直观理解可靠传输
用Python模拟RDT协议:从rdt1.0到3.0的FSM状态机实战
当你第一次学习可靠数据传输协议(RDT)时,是否曾被那些抽象的状态转换图困扰?作为计算机网络课程的核心概念,RDT协议从1.0到3.0的演进过程实际上是一系列解决实际传输问题的巧妙设计。本文将带你用Python构建一个可视化的RDT协议模拟器,通过代码实现发送方和接收方的有限状态机(FSM),让这些抽象概念变得触手可及。
1. 环境准备与基础架构
在开始编码前,我们需要明确模拟器的核心组件。与单纯阅读理论不同,动手实现能让你更直观地理解每个状态转换背后的设计意图。
基础组件清单:
- 状态机核心:用Python类实现发送方和接收方的FSM
- 网络模拟:模拟信道特性(比特差错、丢包)
- 可视化界面:实时显示状态转换和分组流动
- 交互控制:允许手动触发各种异常场景
首先安装必要的Python库:
pip install matplotlib numpy定义基础状态枚举类:
from enum import Enum class State(Enum): WAIT_CALL_0 = 0 # 等待上层调用(序号0) WAIT_CALL_1 = 1 # 等待上层调用(序号1) WAIT_ACK_0 = 2 # 等待ACK(序号0) WAIT_ACK_1 = 3 # 等待ACK(序号1)2. rdt1.0:理想信道的实现
作为最基础的版本,rdt1.0假设信道完全可靠——没有比特差错,没有丢包。虽然现实中不存在这样的网络,但这是理解协议演进的起点。
发送方FSM实现:
class RDTSender1: def __init__(self): self.state = State.WAIT_CALL_0 def rdt_send(self, data): if self.state == State.WAIT_CALL_0: packet = self.make_pkt(data, 0) self.udt_send(packet) self.state = State.WAIT_CALL_1 return True return False # 未就绪 def make_pkt(self, data, seq_num): return {"data": data, "seq": seq_num} def udt_send(self, packet): print(f"[Sender] 发送分组: {packet}") channel.deliver(packet)接收方FSM实现:
class RDTReceiver1: def __init__(self): self.state = State.WAIT_CALL_0 def rdt_rcv(self, packet): if self.state == State.WAIT_CALL_0: data = self.extract(packet) self.deliver_data(data) self.state = State.WAIT_CALL_1 return True return False def extract(self, packet): return packet["data"] def deliver_data(self, data): print(f"[Receiver] 交付数据: {data}")关键观察点:
- 单向状态流转(0→1→0循环)
- 无确认机制(假设传输必然成功)
- 无差错检测和处理逻辑
3. rdt2.0:引入比特差错处理
现实网络中比特差错不可避免。rdt2.0通过ACK/NAK机制实现差错恢复,这是自动重传请求(ARQ)的最简形式。
发送方状态增强:
class RDTSender2(RDTSender1): def __init__(self): super().__init__() self.current_seq = 0 self.saved_packet = None def rdt_send(self, data): if self.state in [State.WAIT_CALL_0, State.WAIT_CALL_1]: packet = self.make_pkt(data, self.current_seq) self.saved_packet = packet # 保存以备重传 self.udt_send(packet) self.state = State.WAIT_ACK_0 if self.current_seq == 0 else State.WAIT_ACK_1 return True return False def handle_ack(self, ack_packet): if self.state in [State.WAIT_ACK_0, State.WAIT_ACK_1]: if self.is_corrupt(ack_packet): print("[Sender] ACK/NAK损坏,等待超时") return if ack_packet["type"] == "NAK": print("[Sender] 收到NAK,重传分组") self.udt_send(self.saved_packet) else: print("[Sender] 收到ACK,准备发送下一分组") self.current_seq = 1 - self.current_seq # 切换序号 self.state = State.WAIT_CALL_0 if self.current_seq == 0 else State.WAIT_CALL_1接收方差错检测逻辑:
class RDTReceiver2(RDTReceiver1): def rdt_rcv(self, packet): if self.is_corrupt(packet): print("[Receiver] 检测到分组损坏,发送NAK") self.udt_send({"type": "NAK"}) return False data = self.extract(packet) self.deliver_data(data) self.udt_send({"type": "ACK"}) return True def is_corrupt(self, packet): # 简化的校验和模拟 return random.random() < 0.3 # 30%概率模拟比特差错协议特性对比表:
| 特性 | rdt1.0 | rdt2.0 |
|---|---|---|
| 差错检测 | ❌ | ✅ |
| 重传机制 | ❌ | ✅ |
| 确认机制 | ❌ | ✅ (ACK/NAK) |
| 状态复杂度 | 低 | 中 |
| 信道要求 | 完全可靠 | 可能出错 |
4. rdt2.1:解决ACK/NAK损坏问题
rdt2.0存在致命缺陷:如果ACK/NAK本身损坏,发送方无法区分新旧分组。rdt2.1通过引入序列号解决这个问题。
发送方关键修改:
class RDTSender21(RDTSender2): def handle_ack(self, ack_packet): if self.is_corrupt(ack_packet): print("[Sender] ACK损坏,重传当前分组") self.udt_send(self.saved_packet) return if ack_packet["seq"] == self.current_seq: print(f"[Sender] 收到正确ACK{self.current_seq},切换状态") self.current_seq = 1 - self.current_seq self.state = State.WAIT_CALL_0 if self.current_seq == 0 else State.WAIT_CALL_1接收方序列号处理:
class RDTReceiver21(RDTReceiver2): def __init__(self): super().__init__() self.expected_seq = 0 def rdt_rcv(self, packet): if self.is_corrupt(packet): print("[Receiver] 分组损坏,丢弃并期待重传") return False if packet["seq"] != self.expected_seq: print(f"[Receiver] 收到冗余分组{packet['seq']},期待{self.expected_seq}") # 发送上次的ACK self.udt_send({"type": "ACK", "seq": 1 - self.expected_seq}) return False data = self.extract(packet) self.deliver_data(data) self.expected_seq = 1 - self.expected_seq self.udt_send({"type": "ACK", "seq": self.expected_seq}) return True典型交互场景模拟:
- 发送方发送seq=0分组
- 接收方正确接收,回复ACK0
- ACK0在传输中损坏
- 发送方超时重传seq=0分组
- 接收方识别冗余分组,再次发送ACK0
- 发送方收到ACK0,转为发送seq=1分组
5. rdt3.0:处理丢包问题的终极方案
rdt2.1仍然无法处理分组完全丢失的情况。rdt3.0引入定时器机制,成为真正的可靠传输协议。
定时器实现要点:
class RDTSender3(RDTSender21): def __init__(self): super().__init__() self.timer = None self.timeout = 3.0 # 3秒超时 def start_timer(self): self.timer = time.time() def check_timeout(self): if self.timer and time.time() - self.timer > self.timeout: print("[Sender] 超时,重传分组") self.udt_send(self.saved_packet) self.start_timer() return True return False def rdt_send(self, data): if super().rdt_send(data): self.start_timer() return True return False def handle_ack(self, ack_packet): if super().handle_ack(ack_packet): self.timer = None # 停止定时器接收方增强:
class RDTReceiver3(RDTReceiver21): def __init__(self): super().__init__() self.last_ack = None def rdt_rcv(self, packet): if self.is_corrupt(packet): return False if packet["seq"] != self.expected_seq: # 立即重发上次的ACK if self.last_ack: self.udt_send(self.last_ack) return False data = self.extract(packet) self.deliver_data(data) self.expected_seq = 1 - self.expected_seq ack = {"type": "ACK", "seq": self.expected_seq} self.last_ack = ack self.udt_send(ack) return True丢包场景测试代码:
# 模拟丢包信道 class LossyChannel: def deliver(self, packet): if random.random() < 0.2: # 20%丢包率 print("[Channel] 分组丢失!") return # 正常传递 receiver.rdt_rcv(packet) if "data" in packet else sender.handle_ack(packet) # 测试用例 channel = LossyChannel() sender = RDTSender3() receiver = RDTReceiver3() # 发送方持续发送数据 for i in range(5): while not sender.rdt_send(f"Data{i}"): sender.check_timeout() time.sleep(0.5) time.sleep(1)6. 可视化与交互设计
为了让学习体验更直观,我们使用matplotlib创建协议状态可视化界面:
import matplotlib.pyplot as plt from matplotlib.animation import FuncAnimation class RDTVisualizer: def __init__(self, sender, receiver): self.fig, (self.ax1, self.ax2) = plt.subplots(2, 1) self.sender = sender self.receiver = receiver def update(self, frame): self.ax1.clear() self.ax2.clear() # 绘制发送方状态 self.ax1.set_title(f"发送方状态: {self.sender.state.name}") self.ax1.text(0.5, 0.5, f"当前序号: {self.sender.current_seq}", ha='center', va='center') # 绘制接收方状态 self.ax2.set_title(f"接收方状态: 期待序号{self.receiver.expected_seq}") anim = FuncAnimation(plt.gcf(), RDTVisualizer(sender, receiver).update, interval=1000) plt.show()交互控制台功能:
- 手动触发比特差错
- 调整丢包概率
- 控制传输速度
- 查看状态转换历史记录
在实际教学中,这种可视化模拟器能显著提升学生对以下概念的理解:
- 有限状态机的实际应用
- 序列号在可靠传输中的关键作用
- 定时器如何解决丢包问题
- 停等协议的效率瓶颈
通过这个项目,你不仅理解了RDT协议的设计精髓,还掌握了如何将网络协议理论转化为可执行的代码模型。这种技能对于深入理解TCP等实际协议的工作机制至关重要。
