GBN协议与滑动窗口:基于Python的可靠传输模拟与实践

GBN协议与滑动窗口:基于Python的可靠传输模拟与实践 简介面向计算机网络课程实验的Python源码包聚焦数据链路层GBN后退N帧协议并以可靠文件传输为最终实现效果。资料面向正在完成课程设计、毕业设计或希望弄懂滑动窗口与差错控制的计算机相关专业学生也适合教师课堂演示和初入网络编程的开发者进阶学习。压缩包共21个文件、体积仅22KB其中12个Python脚本构成主体实现协议逻辑、自动测试与结果分析7个INI配置文件用于调节窗口大小、丢包率等关键参数同时提供README与依赖清单帮助快速理解项目结构并搭建运行环境。目前已有425人浏览学习是从课程实验角度验证GBN协议机制的实用参考。源码按GbnTool模块化组织清晰分离帧封装、字节转换、差错注入、日志记录等职责既可一键运行观察文件可靠传输的完整过程也可通过修改参数对比不同信道条件下协议的吞吐与丢包表现能够直接服务于实验报告、课程答辩并为后续扩展选择性重传、拥塞控制等机制打下基础。1. GBN不只是教材上的三张图而是一份能跑通的文件传输协议数据链路层的可靠传输协议里GBNGo-Back-N回退 N 步是滑动窗口机制的典型代表几乎所有计算机网络教材都会画它的时序图发送窗口大于 1接收窗口等于 1一旦超时就把窗口内所有已发送但未确认的帧全部重传。但纸上推演和真正跑起来是两码事——你需要在 UDP 这种不可靠信道上实现帧的封装拆解、校验、缓冲区管理和超时重传还需要证明它确实能可靠地传输一个文件而不只是几个数据包。这份基于 Python 的模拟源码正是为此设计的它用 UDP 模拟物理层的比特错误和丢包在此之上完整实现了 GBN 协议栈并附带自动化测试和错误注入工具非常适合计算机网络课程实验、考研复试准备或想搞懂滑动窗口背后工程细节的人。与其对着时序图猜不如直接看发送指针base和nextseq是怎么随着丢包和超时跳动的。2. Python 模拟 GBN 的帧格式与滑动窗口核心设计2.1 为什么选择 UDP 来模拟物理层不可靠信道真实链路层的 GBN 是跑在网卡和驱动里的但课程实验要在普通用户态程序中观察协议行为就必须自己构造一个可控的“不可靠信道”。源码中用UdpTool.py承担这个角色它按config.ini里配置的丢包率和误码率随机丢弃或篡改数据报从而把链路层的噪声模拟到应用层。这样做的好处是你可以精确控制丢包发生在哪个序号帧上复现教材里的经典场景而不必在实验室里真去折断网线。UdpTool.py的对外接口很干净class UdpTool: def send(self, addr, payload: bytes): # 随机决定是否丢包 if RandomTool.hit(self.loss_rate): LogTool.warn(f[Udp] drop packet, payload_len{len(payload)}) return # 随机决定是否翻转字节 if RandomTool.hit(self.corrupt_rate): payload ErrorTool.corrupt(payload) self.sock.sendto(payload, addr)参数说明loss_rate控制丢包概率corrupt_rate控制单帧污染概率两者独立触发这模拟了物理信道的两类典型故障。ErrorTool.corrupt()会随机选中 payload 中的一个字节做按位取反确保校验和机制能被真实触发而不是靠猜。2.2 帧格式设计序列号、校验码和“谁拥塞就重传谁”FrameTool.py定义了本实验使用的帧格式。GBN 对帧格式的约束很明确发送方发出的数据帧要有序列号和校验码接收方回 ACK 时也要带上累计确认号。代码里的帧结构如下# 数据帧: version(1B) | type(1B) | seq(2B) | length(2B) | payload(bytes) | checksum(2B) # ACK帧: version(1B) | type(1B) | ack(2B) | length(2B) | checksum(2B) DATA_FRAME_TYPE 0x01 ACK_FRAME_TYPE 0x02 def pack_data_frame(seq: int, payload: bytes) - bytes: length len(payload) header struct.pack(!BBHH, VERSION, DATA_FRAME_TYPE, seq, length) checksum FrameTool.crc16(header payload) return header payload struct.pack(!H, checksum) def parse_frame(raw: bytes): version, ftype, seq, length struct.pack(!BBHH, raw[:6]) checksum struct.unpack(!H, raw[6 length : 8 length])[0] if checksum ! FrameTool.crc16(raw[:6 length]): return None # 校验失败调用方按损坏帧处理 return {type: ftype, seq: seq, payload: raw[6:6 length]}使用说明帧头用!BBHH固定 6 字节网络字节序保证跨平台解析一致。seq是 16 位无符号整数窗口大小配置受它限制。校验用 CRC16 而不是简单累加和原因在于 CRC 能捕捉突发比特翻转而累加和在报文长度变化时容易漏检。2.3 发送窗口与单一定时器GBN 区别于 SR 的关键GBN 的经典实现里只需要一个定时器——它只负责窗口内最旧未确认帧base的超时而不是给每个帧单独挂计时器。这就是它比选择重传SR简单的地方也是性能上最吃亏的地方一旦base超时发送方要回退重传base到nextseq-1的所有帧。# GBN 发送方核心状态 self.base 0 # 窗口下界最小未确认帧序号 self.nextseq 0 # 窗口上界下一个待发送帧序号 self.window config.window_size def send_data(self, data_block: bytes): if self.nextseq - self.base self.window: return False # 窗口满暂停推送 frame FrameTool.pack_data_frame(self.nextseq, data_block) self.udp.send(self.peer_addr, frame) self.buffer[self.nextseq % self.window] (frame, data_block) self.nextseq 1 self.timer.start_once(timeout_msconfig.timeout_ms)这段代码揭示了一个容易被忽略的细节buffer用的是环状缓冲长度等于窗口大小所以序号取模是安全的。窗口满时send_data返回False上层文件传输模块会停止从文件读取这保证了缓冲区不会无限增长。定时器的实现只有一个实例超时回调里执行self.base self.base不改变序号而是重发self.buffer里所有base到nextseq-1的帧——这就是“回退 N”名称的来源。3. 模块划分与 main.py 启动流程从配置到文件落地的完整链路3.1 GbnTool 模块族每个工具类解决一类问题解压后的目录里GbnTool包内有九个工具模块职责划分是很清晰的课堂设计风格AddrTool管理发送端和接收端的 IP/端口FileTool负责把大文件切成数据块再重组ByteTool处理字节序和填充ErrorTool提供比特翻转和丢包注入ConfigTool读 ini 配置RandomTool为错误注入提供随机种子LogTool输出带时间戳和模块名的日志UdpTool封装 socketFrameTool是上一章讲的帧编解码。这种分层的做法值得模仿协议状态机和 IO 分离测试时才不需要真的发送数据。main.py是实验的入口它识别--role参数一个进程当发送方另一个当接收方# 终端 1启动接收方 python main.py --role receiver --config config.ini # 终端 2启动发送方接收方绑定在 127.0.0.1:8001发送方自动读取文件并分块发送 python main.py --role sender --config config.ini --input ./test_data.bin --output ./recv_test_data.binreceiver端启动后会打印监听地址然后进入recv_file()循环。sender端用FileTool.split_file()把文件切块逐块交给send_data()全部确认后再发一个 EOF 帧通知对端结束。代码里的recv_file()循环逻辑值得细看def recv_file(self, output_path: str): with open(output_path, wb) as f: while True: raw, addr self.udp.recv(timeout_msself.block_timeout_ms) frame FrameTool.parse_frame(raw) if frame is None: self.stats.corrupt_packets 1 # 对损坏帧直接丢弃等待发送方超时重传 continue if frame[type] DATA_FRAME_TYPE: if frame[seq] self.expected_seq: f.write(frame[payload]) self.expected_seq 1 self.udp.send(addr, FrameTool.pack_ack_frame(self.expected_seq - 1)) else: # 乱序或重传帧丢弃但补发当前期望帧的 ACK self.udp.send(addr, FrameTool.pack_ack_frame(self.expected_seq - 1))3.2 接收端的“窗口为 1”体现上面代码最关键的一行是if frame[seq] self.expected_seq接收端只能接受按序到达的帧任何乱序帧都被丢弃同时补发当前期望序号的 ACK。这就是 GBN“接收窗口 1”的代码形态也正是它和 SR 协议最本质的分野。你可能会问补发 ACK 有意义吗有——它能在网络没有丢包但 ACK 帧自身丢失时让发送方收到重复 ACK 而提前推进窗口减少不必要超时重传。虽然本科教材不一定强调但工程实现里这行代码能显著提升吞吐。文件最后被FileTool.merge_file()合并同时计算接收端文件的 SHA256 并和发送端对比一致的打印校验通过。实验中如果你看到发送端日志比接收端多出若干条timeout resend那不是 bug而是协议在正确工作——这正是我们要在第 4 章用错误注入来验证的行为。4. 通过 config.ini 与 ErrorTool 构造网络故障场景4.1 配置参数逐项说明config.ini是实验入口的“水量开关”所有故障参数都收敛在这里[GBN] window_size 5 ; GBN发送窗口编号从0开始最大seq为65535 timeout_ms 2000 ; 超时定时器时间需大于RTT [Channel] loss_rate 0.05 ; 丢包率0表示不丢包 corrupt_rate 0.01 ; 误码率按帧为单位翻转1字节 delay_ms 20 ; 模拟链路传播延迟影响RTT计算 [File] block_size 4096 ; 文件分块大小每块封装为一帧 input_file test.bin output_file recv_test.bin [Log] log_level DEBUG ; DEBUG|INFO|WARN|ERROR log_file runtime.log参数调整对协议行为的影响window_size设 5 时PIPELINE 深度为 4发送序号从 0 开始窗口上界为 base5紧接窗口满后会停止读取文件。若timeout_ms设置过小发送方会把网络延迟误判为丢包导致频繁重传若设置过大则真实丢包后要等很久才重传吞吐率崩塌。一个可用的经验值是timeout_ms (RTT 处理时延) × 2再乘 1.5 的安全系数。4.2 AutoTest 自动化写一个能跑一整夜的错误场景矩阵AutoTest目录里的autotest.py是实验报告的得力助手。它按standard_config.ini里的场景参数进行批量测试核心逻辑是遍历丢包率序列和窗口大小序列每次跑完同一组数据后记录耗时和重传次数# autotest.py 片段对每档参数做N次重复测试 scenarios [ {loss_rate: 0.00, corrupt_rate: 0.00, window_size: 3}, {loss_rate: 0.05, corrupt_rate: 0.01, window_size: 5}, {loss_rate: 0.10, corrupt_rate: 0.02, window_size: 7}, {loss_rate: 0.20, corrupt_rate: 0.05, window_size: 9}, ] for sc in scenarios: cfg ConfigTool.load(standard_config.ini) cfg.update(sc) metrics run_one_round(cfg) print(f{sc} - throughput{metrics[throughput]:.2f} KB/s, fretransmit_ratio{metrics[retransmit_ratio]:.3f})run_one_round内部会启动接收子进程、发送子进程、等待完成、对比文件哈希并把吞吐量和重传比例写到一个 CSV。这样一个夜间循环跑完 4 个档位 × 5 次重复就能生成一张“GBN 协议性能随丢包率变化”的曲线这在实验报告里是很有说服力的数据。5. 用 LogTool 与 analyse.py 定位 GBN 回退重传行为5.1 从调试日志还原一次完整的超时重传时间线GBN 排错最怕的是一团乱麻的“现象级”日志。LogTool.py默认输出格式为[时间戳][模块名]|[级别]|内容模块名是 UdpTool、GbnSender、GbnReceiver 三个维度所以你可以用 grep 把交互过程还原成时序图。先启动接收方再用DEBUG级别跑一个 5% 丢包的测试然后提取发送方日志grep GbnSender runtime.log | grep -E timeout|resend|ack received | head -50我一般会重点关注四类日志[timeout expired] base12、[resend] seq12..16、[ack received] ack16、[window slide] base12 - 17。把这四类出现顺序连起来你就能肉眼验证 GBN 的核心行为——超时后是否一次重传了整个窗口而不是只传了超时的那一帧。如果发现只重传了一帧那说明发送方被误实现成了 SR 风格接收端就会因为expected_seq收不到期待帧而一直补发 ACK吞吐反而上不去。5.2 用 analyse.py 做数据质量校验和统计输出analyse.py读取日志和测试结果输出两类关键指标数据完整性和传输效率。它不是一个 GUI 工具而是一个命令行统计脚本python analyse.py --log runtime.log --output summary.txt python analyse.py --log runtime.log --json --output metrics.jsonsummary.txt会给出总数、重传次数、重传比率、平均 RTT 和吞吐率。拿到这些数字后你可以无风险地做一次“三分钟验证实验”把loss_rate从 0 调到 0.05文件哈希不变、吞吐率应明显下降、重传比率上升这是协议可靠性的三个并存表现。如果只看到哈希正确但重传比率没有上升你应怀疑错误注入没有生效这时回到UdpTool.send()查看丢包是否真的产生而不是看配置表面数值。整个实验里最不起眼但最有价值的一步是你亲自确认了“增加重传次数是换取可靠性的必然代价”这个代价曲线在期末面试和保研机试中都是高频考点GBN 的发送利用率为W / (1 2a)其中a是传播延迟与发送延迟的比值。把window_size、delay_ms代入这个公式你会发现模拟的结果和理论曲线的偏差几乎全部来自 CRC16 校验失败的帧被重复确认带来的额外开销这就是 GBN 在真实链路里和课本公式之间的裂缝所在。本文还有配套的精品资源点击获取