ARQ协议实战:如何用Python模拟停止等待和连续ARQ的可靠性传输?
如果你曾经尝试过在不可靠的网络环境中传输文件,比如通过一个信号不稳定的Wi-Fi发送一个大文档,你可能会遇到文件损坏或者传输中断的情况。这时候,网络协议中的“可靠性”机制就派上了用场。在计算机网络的底层,尤其是在数据链路层和传输层,工程师们设计了一系列精巧的协议来确保数据包能够准确无误地到达目的地,即使传输路径本身并不可靠。
自动重传请求(ARQ) 就是这类可靠性机制的核心。它的思想朴素而有效:发送方发出数据后,必须等待接收方的确认信号;如果超时未收到确认,就认为数据丢失或出错,然后重新发送。这听起来简单,但在高延迟、易丢包的真实网络里,如何平衡效率与可靠性,却衍生出了多种不同的实现策略。
对于开发者而言,仅仅理解ARQ的理论框图是远远不够的。协议的生命力在于实现。今天,我们就抛开教科书上的图示,直接动手,用Python搭建一个模拟网络环境,亲手实现停止等待ARQ和连续ARQ(这里特指回退N帧协议),并在一个设定丢包率为10%的“恶劣”信道中,直观地对比它们的性能差异。通过代码,你将看到超时重传、滑动窗口这些抽象概念如何转化为具体的逻辑判断和状态维护。
1. 构建基础:模拟不可靠网络与数据帧
在开始实现具体的ARQ协议之前,我们需要一个实验场——一个可以模拟数据包丢失、延迟等网络行为的虚拟环境。直接使用本机的TCP/IP栈太“完美”了,无法控制丢包。因此,我们先构建一个简单的网络模拟层。
这个模拟层的核心是一个UnreliableChannel类。它内部维护两个队列,分别模拟从发送方到接收方、以及反向的链路。数据包(在我们这里就是“帧”)在入队时,会根据预设的丢包率随机被丢弃。
import random
import time
from dataclasses import dataclass
from typing import Any, Optional, Callable
from enum import Enum
class FrameType(Enum):
"""定义帧的类型:数据帧或确认帧"""
DATA = "DATA"
ACK = "ACK"
@dataclass
class Frame:
"""数据帧/确认帧的基本结构"""
seq_num: int # 序列号
data: Any # 承载的数据(对ACK帧,此字段可为None)
frame_type: FrameType
class UnreliableChannel:
"""模拟一个具有丢包率的不可靠信道"""
def __init__(self, loss_probability: float = 0.1):
"""
初始化信道
:param loss_probability: 丢包率,默认为10%
"""
self.loss_prob = loss_probability
self.send_to_receiver = [] # 发往接收方的队列
self.send_to_sender = [] # 发往发送方的队列
self.stats = {"sent": 0, "lost": 0, "delivered": 0}
def send(self, frame: Frame, to_receiver: bool = True):
"""向信道发送一个帧。to_receiver为True表示发往接收方,否则发往发送方。"""
self.stats["sent"] += 1
if random.random() < self.loss_prob:
# 模拟丢包
self.stats["lost"] += 1
return False
# 成功入队
target_queue = self.send_to_receiver if to_receiver else self.send_to_sender
target_queue.append(frame)
return True
def receive(self, expected_for_receiver: bool = True) -> Optional[Frame]:
"""从信道接收一个帧。expected_for_receiver为True表示接收方尝试接收,否则为发送方尝试接收。"""
source_queue = self.send_to_receiver if expected_for_receiver else self.send_to_sender
if source_queue:
frame = source_queue.pop(0)
self.stats["delivered"] += 1
return frame
return None
def get_stats(self):
"""获取当前信道的统计信息"""
return self.stats.copy()
有了这个信道,我们就能在一个可控的环境下测试协议了。接下来,我们定义协议参与方的基类,它们将共享一些基础功能,比如通过信道收发帧、维护日志等。
class ProtocolEntity:
"""协议实体(发送方或接收方)的基类"""
def __init__(self, name: str, channel: UnreliableChannel):
self.name = name
self.channel = channel
self.log_buffer = []
def log(self, message: str):
"""记录日志,附带时间戳和实体名称"""
timestamp = time.strftime("%H:%M:%S", time.localtime())
entry = f"[{timestamp}] {self.name}: {message}"
self.log_buffer.append(entry)
print(entry)
def get_logs(self):
"""获取所有日志"""
return self.log_buffer
2. 实现停止等待ARQ协议
停止等待协议是ARQ家族中最简单的一位成员。它的规则非常直接:
- 发送方发送一个数据帧,然后启动定时器。
- 进入等待状态,直到:a) 收到对应的确认帧(ACK);b) 定时器超时。
- 如果收到ACK,则发送下一个帧;如果超时,则重传当前帧。
这种“发一个,等一个”的模式,使得其发送窗口和接收窗口大小均为1。实现的关键在于对序列号和定时器的管理。
2.1 发送方逻辑实现
发送方需要维护当前待发送帧的序列号、一个定时器状态,以及一个标志来判断是否正在等待ACK。
class StopAndWaitSender(ProtocolEntity):
"""停止等待协议的发送方实现"""
def __init__(self, channel: UnreliableChannel, data_to_send: list):
super().__init__("StopWait-Sender", channel)
self.data = data_to_send # 要发送的数据列表
self.next_seq = 0 # 下一个要发送的序列号
self.expected_ack = None # 期望收到的ACK号
self.timer_start = None # 定时器启动时间
self.timeout_duration = 2.0 # 超时时间(秒)
self.is_waiting = False # 是否处于等待ACK状态
self.sent_count = {0: 0, 1: 0} # 统计各序列号帧的发送次数
def send_frame(self, seq_num: int):
"""构造并发送一个数据帧"""
frame = Frame(seq_num=seq_num, data=self.data[seq_num % len(self.data)], frame_type=FrameType.DATA)
if self.channel.send(frame, to_receiver=Tru


5841

被折叠的 条评论
为什么被折叠?



