Python实现UDP可靠传输:停等、GBN、SR协议课设全指南

发布时间:2026/10/19 10:39:39
Python实现UDP可靠传输:停等、GBN、SR协议课设全指南 简介基于Python实现可靠数据传输协议的课程设计资源包面向计算机网络或协议编程课程的本科学生与自学者。整套实验围绕UDP之上的可靠性设计完整覆盖停等、回退NGBN和选择重传SR三个递进阶段包含单向与双向数据传输、模拟丢包验证以及C/S结构文件传输等实验内容可帮助读者系统理解滑动窗口与差错控制原理。包体共14个文件其中8个Python脚本为核心协议实现3个文本文件存放收发数据样例另有Word版实验报告、Markdown说明与许可证文件压缩包仅723KB结构清晰便于直接运行。目前已有596人浏览学习。通过源码与文档可快速复现收发双方的协议状态处理、超时重传与窗口滑动逻辑并提供了从停等协议扩展至回退N、再改进为选择重传的完整思路适合作为课程设计参考或协议实战起点。1. 可靠传输课程设计为什么从 UDP 而不是 TCP 开始课程设计拿到「基于 Python 实现可靠数据传输协议」这个题多数人的第一反应是UDP 本身不可靠为什么偏要在它上面做可靠传输这正是这份设计最有价值的地方——TCP 的可靠机制对使用者是个黑匣子而实验要求从零实现停等协议、GBN 协议再改造成 SR 协议等于把滑动窗口、超时重传、序号与确认这些概念全部亲手做一遍而不是背一遍。这套方案对应编号 100010493 的课设资源覆盖从单工到双工、再到 C/S 文件传输应用的完整链路适合正在做网络课设或毕设的学生也适合要快速上手 UDP 可靠传输的开发者。下面按「需求拆解 → 协议设计 → 丢包验证 → 双向与文件传输 → 避坑排查 → 验收答辩」推进每一章都落到可运行的代码和可复现的实验数据上照着改就能交。2. 协议总体设计停等、GBN、SR 三层递进与工程模块划分2.1 需求拆解六个实验目标怎么组成四条递进路线实验内容里列了八个条目但归纳下来是四条递进路线停等单工 → 停等双工 → GBN 单工/双工 → SR 改造。为什么这么排因为每个阶段只引入一个新概念后面的协议都能复用前面的代码。停等阶段只处理「发一个、等一个确认」状态机最小GBN 在停等基础上加发送窗口第一次出现连续发送和累积确认SR 又在 GBN 基础上把接收方的丢弃策略改成缓存解决「一个包丢了要重传一堆」的带宽浪费。做课设最容易犯的错是一上来就写 SR写到一半发现接收窗口和缓存逻辑根本理不清最后全部推翻。单工和双工的差别也要提前想清楚。单工只验证可靠传输的基本机制server 只管发、client 只管收双工意味着两端都要能发能收如果不做收发分离就很容易卡死。文件传输则是把协议捆绑到具体应用上读文件、分块、按协议发送、接收落盘最后比对两个文件是否一致这是最直观的验收证据。2.2 技术选型为什么是 socket 加自造分组格式协议实现最忌讳用现成库课设的核心目的就是让你自己写一遍。Python 标准库里的 socket 模块提供 UDP 收发原语struct 模块负责二进制分组编解码zlib 提供 crc32 校验这三个库足够完成全部实验不引入第三方依赖。分组格式是三种协议共用的地基我按网络字节序设计了一个 15 字节头加 payload 的格式。字段长度说明checksum4Bcrc32 校验值覆盖整个分组体seq4B数据分组序号从 0 递增ack4B确认序号-1 表示不带确认flags1B0x01 DATA / 0x02 ACK / 0x04 FINlength2Bpayload 实际长度payload0~1500B文件分块内容或空校验和放在最前面是有意的接收端拿到报文先校验校验失败直接丢弃绝不拿坏数据去拆字段。seq 用 32 位无符号整数传几百 MB 的文件都不会回绕这点比课设里常见的 8 位序号稳妥得多。2.3 模块职责划分server、client、util、packet 各管什么工程按职责分成四个模块每个模块只做一类事模块职责关键函数packet.py分组构造、解析、crc32 校验build_packet()、parse_packet()server.py发送端实现停等 / GBN / SR 三种模式stop_wait_send()、gbn_send()client.py接收端落盘 recv 数据、维护接收窗口stop_wait_recv()、SRReceiverconfig.pyIP、端口、超时、窗口、丢包率集中配置load_config()config 集中管理是调试效率的关键。丢包率、超时阈值、窗口大小全部放配置文件里改参数不用翻业务代码答辩时现场把丢包率从 0 调到 0.3 就能演示协议行为变化。发送端读 server_data.txt接收端写 client_recv.txt两边文件一比对就是验收依据。2.4 统一分组构造与解析packet 模块的公共代码# src/protocol/packet.py —— 三种协议共用同一套分组格式 import struct import zlib FLAG_DATA 0x01 FLAG_ACK 0x02 FLAG_FIN 0x04 def build_packet(seq, payloadb, ack-1, flagsFLAG_DATA): 构造分组seq 必传ack 默认 -1 表示纯数据包。 body struct.pack(!IIBH, seq, ack, flags, len(payload)) payload checksum zlib.crc32(body) 0xffffffff return struct.pack(!I, checksum) body def parse_packet(raw): 解析分组先校验再拆字段校验失败返回 None由上层丢弃。 if len(raw) 15: return None checksum struct.unpack(!I, raw[:4])[0] body raw[4:] if (zlib.crc32(body) 0xffffffff) ! checksum: return None seq, ack, flags, length struct.unpack(!IIBH, body[:11]) payload body[11:11 length] return seq, ack, flags, payload两点设计说明。一是!指定大端字节序这是网络协议默认规则用本地小端序打包在跨平台跑的时候会翻车。二是先拼 body 再算 crc32发送端和接收端的计算范围严格一致避免出现「发送方校验的是 payload接收方校验的是整个包」这种最隐蔽的 bug。3. 停等协议实现状态机、核心代码与丢包模拟验证3.1 停等状态机发送方一个循环、接收方一个循环停等协议是全套设计里唯一能用两个循环讲清楚的状态机。发送方只有三个状态发送分组、等待 ACK、超时重发。收到匹配的 ACK 就推进序号收到不匹配的 ACK 就忽略继续等超时就把同一个分组再发一遍直到确认。接收方更简单只维护一个期望序号 expect。收到序号等于 expect 的分组就交付并递增收到重复分组不交付但一定要回 ACK。这个「重复也回 ACK」的细节是停等能收敛的关键如果发送方发的数据没丢、只是 ACK 丢了发送方超时重传后接收方必须用重复 ACK 让发送方走出等待循环。3.2 发送端与接收端核心代码stop_wait_send 和 stop_wait_recv# src/protocol/server.py —— 停等发送端 import socket from util.config import TIMEOUT, CLIENT_ADDR from protocol.packet import build_packet, parse_packet def stop_wait_send(sock, data_blocks): 停等发送每个分组发送后阻塞等 ACK超时就重发同一分组。 base 0 while base len(data_blocks): pkt build_packet(seqbase, payloaddata_blocks[base]) while True: sock.sendto(pkt, CLIENT_ADDR) # 发出当前分组 try: sock.settimeout(TIMEOUT) ack_raw, _ sock.recvfrom(64) ack_pkt parse_packet(ack_raw) if ack_pkt and ack_pkt[1] base: # ack 字段回显了当前序号 base 1 # 收到确认推进下一个 break except socket.timeout: pass # 超时后回到循环头发送同一分组# src/protocol/client.py —— 停等接收端 from protocol.packet import build_packet, parse_packet, FLAG_ACK def stop_wait_recv(sock, sender_addr, total_blocks): 停等接收只接受期望序号其余一律回 ACK 但不交付。 expect 0 while expect total_blocks: raw, addr sock.recvfrom(2048) pkt parse_packet(raw) if pkt is None: continue # 校验失败静默丢弃 seq, _, _, payload pkt if seq expect: # 是期望的分组顺序落盘 write_block(payload, expect) # 写到 recv/client_recv.txt expect 1 # 期望或重复分组都回 ACK回显 seq重复 ACK 帮发送方退出超时 sock.sendto(build_packet(seq0, ackseq, flagsFLAG_ACK), sender_addr)这段代码里最容易漏的是接收方对重复分组回 ACK。很多同学只对期望分组回 ACK结果数据包没丢、只是 ACK 丢了一次发送方重传后接收方又不回 ACK两边就僵住了。停等的可靠性不只在「丢包重传」还在「重复确认帮对方解围」。3.3 丢包模拟与有效性验证把信道变坏再看协议怎么收敛验证协议有效性的方法是主动把信道变坏在发送出口加一个随机丢包开关。# src/util/network.py —— 带丢包率的发送封装 import random from util.config import LOSS_RATE def sendto_maybe_loss(sock, pkt, addr): 以 LOSS_RATE 概率丢弃分组其余情况正常发送。 if random.random() LOSS_RATE: return # 模拟信道丢包什么都不发 sock.sendto(pkt, addr)测试分三档跑丢包率 0% 作为基线10% 验证小扰动下协议收敛30% 观察重传压力和吞吐量下降。每档跑完比对 server_data.txt 与 client_recv.txt 是否字节一致一致就说明协议在丢包信道上仍然可靠。提示丢包模拟只该作用在数据分组上ACK 别一起丢否则测出来的不是协议可靠性而是「ACK 丢失导致的无谓重传」这一条在第 5 章会展开讲。跑实验时先启动接收端再启动发送端虽然发送端超时会重传但先收后发能省掉开头一批无效报文日志也更干净。用这样两条命令分别开两个终端# 终端 A先启动接收端 python -m src.protocol.client # 终端 B再启动发送端 python -m src.protocol.server4. 滑动窗口协议实现GBN 回退 N、SR 选择性重传与双向改造4.1 GBN 发送方累积确认与超时回退 N 的实现GBN 和停等的差别只有一个发送窗口。停等是窗口为 1 的特例GBN 允许窗口内多个分组连续发送接收方对乱序分组直接丢弃用累积 ACK 告诉发送方「我期望的下一个序号是什么」。# src/protocol/server.py —— GBN 发送端窗口大小取 4 def gbn_send(sock, data_blocks, window_size4): base 0 # 最早未确认的序号 next_seq 0 # 下一个要发送的序号 while base len(data_blocks): # 窗口未满就持续发送base window_size 是窗口上沿 while next_seq min(base window_size, len(data_blocks)): sock.sendto(build_packet(seqnext_seq, payloaddata_blocks[next_seq]), CLIENT_ADDR) next_seq 1 try: sock.settimeout(TIMEOUT) ack_raw, _ sock.recvfrom(64) pkt parse_packet(ack_raw) if pkt and pkt[1] base: base pkt[1] # 累积确认所有小于 base 的序号都确认了 except socket.timeout: # 回退 N从 base 开始重发窗口内全部分组 next_seq base for i in range(base, min(base window_size, len(data_blocks))): sock.sendto(build_packet(seqi, payloaddata_blocks[i]), CLIENT_ADDR) next_seq 1累积确认的理解是这章的重点。接收方回 ACK 期望序号比如收到 0、1、2 三个分组后回 ack3发送方把 base 直接推进到 3等于一条 ACK 确认了前面三个分组。超时后回退 N 的逻辑是 next_seq 退到 base再走一遍发送循环把窗口里没被确认的分组全部重发一遍。链路利用率也值得在报告里写一笔停等协议的利用率近似 1/(12a)a 是传播延迟和传输时间的比值引入窗口 W 后利用率能到 W/(12a)。这就是为什么窗口越大吞吐越高但窗口超过带宽延迟积后收益会饱和。4.2 GBN 接收方到 SR 接收方从丢弃到窗口缓存的改造GBN 接收方是一个只认连续序号的循环凡是乱序分组一律丢弃只回「最后正确接收」的 ACK。它的代码比停等还简单把 3.2 里 stop_wait_recv 的expect 1改成expect seq 1就能工作但要传一个 ACK 给发送方推进 base。SR 的改造核心在接收方不再丢弃乱序分组而是放进窗口缓存凑齐连续段再交付。发送方也不需要回退 N每个分组独立超时、独立重传代价是接收缓存和多个定时器。# src/protocol/client.py —— SR 接收方窗口缓存 class SRReceiver: def __init__(self, window_size4): self.window_size window_size self.base 0 # 最早未交付的序号 self.cache {} # seq - payload缓存乱序分组 def on_packet(self, seq, payload): # 窗口内的分组缓存并尝试连续交付 if self.base seq self.base self.window_size: self.cache[seq] payload if seq self.base: while self.base in self.cache: write_block(self.cache.pop(self.base), self.base) self.base 1 return seq # 选择性确认收到哪个就确认哪个 # 已经交付过的旧分组只重发 ACK防止发送端误超时 if self.base - self.window_size seq self.base: return seq return NoneGBN 到 SR 的改造点就三处接收方加缓存、ACK 从累积改成选择、发送方从「超时重发整个窗口」改成「超时只重发超时序号」。答辩时把这三处差异讲清楚比背一遍教科书定义有说服力得多。4.3 双向传输改造捎带确认与双工状态机双向传输是课设里最容易卡住的一个里程碑问题不在协议本身而在工程结构。最简单的做法是收发分离发送线程负责按窗口发数据和处理超时重传接收线程负责收数据、回 ACK两边互不阻塞。# 双向传输用两个线程分离收发避免互相等死锁 import threading def run_bidirectional(sock, data_blocks, peer_addr): recv_thread threading.Thread(targetrecv_loop, args(sock, peer_addr)) recv_thread.start() # 接收线程先起来 send_loop(sock, data_blocks, peer_addr) # 主线程跑发送逻辑如果要做捎带确认减少报文数数据分组里的 ack 字段本来就有用武之地收到对端数据后不单独回 ACK而是把确认序号塞进下一个数据分组的 ack 字段。但捎带确认有个坑两端如果同时在等对方的数据分组来「带确认」就会进入互相等待的死锁。课设里用收发双线程最稳妥捎带确认可以作为报告里「进一步优化」的章节来写不要在演示时现场切。5. 常见问题排查与避坑UDP 可靠传输容易翻车的五个点5.1 大文件传一半卡死序号回绕让窗口判断失效现象传几百字节的小文件一切正常换个大文件传一半接收端突然开始丢弃所有分组日志里全是重传。原因序号用了 8 位或做了取模序号回绕后base seq base window_size这个窗口判断完全失效新序号被当成旧序号丢弃。解决序号用 32 位无符号整数窗口判断改成模运算(seq - base 2**32) % 2**32 window_size。课设传的文件再大也到不了 4G 个分组32 位序号足够。5.2 超时重传风暴RTO 设得比 RTT 还短现象网络稍微抖动一下终端刷出大量重复分组接收文件出现重复块吞吐量骤降。原因TIMEOUT 拍脑袋设成 0.01 秒比实际往返时间还短分组还没到对端就超时重传再加上 GBN 超时要重传整个窗口形成雪崩。解决先用一个不带丢包的脚本量出平均 RTT把 TIMEOUT 设成 2 倍 RTT 左右连续超时时做指数退避第一次等 RTO第二次等 2 倍 RTO。超时不是越小越好比 RTT 还小的超时就是给自己制造丢包。5.3 文件内容对不上校验和计算范围不一致现象传完比对 client_recv.txt 与 server_data.txt中间有一段乱码或错位但协议全程没有报错。原因发送方按 payload 算 crc32接收方按整个分组算两边校验范围不一致或者 struct 打包时一个用大端一个用本地字节序。解决统一按 2.4 的做法先struct.pack(!IIBH, seq, ack, flags, length)拼出 body再对 body 算 crc32接收端先校验再拆字段任何一端改了打包顺序另一端立刻能发现。5.4 双向传输互相等死锁收发逻辑串在同一个循环里现象改成双向传输后程序偶尔卡住两端都停在 recvfrom 的阻塞调用上按 CtrlC 才退出。原因收发逻辑写在同一个循环里两边同时进入「等对端数据」的状态没有一方主动发包协议就死锁了。解决用两个线程分离收发接收线程起来后主线程跑发送逻辑或者在 config 里加一个PEER_TIMEOUT超过阈值没收到对端数据就主动发一个保活分组打破僵局。5.5 丢包率调高后吞吐量归零把 ACK 也丢了现象丢包率从 10% 调到 30%协议还能传完但耗时暴涨一个数量级看起来像死循环。原因丢包函数对所有 sendto 一视同仁ACK 分组也丢一个 ACK 丢失就触发一次超时重传链路大量时间花在重发上。解决丢包模拟只作用于 DATA 分组ACK 默认可靠如果真想模拟双向丢包单独加一个ACK_LOSS_RATE参数。答辩演示时先展示「只丢数据」的收敛过程再展示「数据和 ACK 都丢」的性能对比能直观说明 ACK 丢失对吞吐的影响。提示这五条里前两条最容易把排查带偏遇到卡死的现象先看序号和超时别急着怀疑丢包函数。6. 测试与验收功能用例、性能数据与课设答辩清单6.1 验收清单六项功能测试与通过标准每一档实验都对应一个可复现的验收用例按下面这张表逐项打勾比自己说「跑通了」有说服力得多。验收项操作通过标准停等单工0% 丢包传输 server_data.txtclient_recv.txt 与源文件字节一致停等丢包LOSS_RATE10% 重跑文件一致日志出现重传记录停等双工两端互发小文件两端收到的文件均一致GBN 单工0% 和 10% 丢包各跑一遍文件一致无重复块SR 改造30% 丢包下与 GBN 对比SR 重传次数少于 GBN耗时下降文件传输应用传 5MB 随机文件校验一致打印耗时和吞吐量6.2 答辩演示先跑基线再跑丢包用数据说话答辩时不要上来就贴代码先跑一个 0% 丢包基线再现场把 LOSS_RATE 改成 0.1 重跑用重传日志和耗时对比说明协议确实在工作。报告里放一张停等、GBN、SR 在 10%、30% 丢包率下的耗时对比表比任何文字描述都直观。讲原理时抓住三个「为什么」停等为什么慢因为链路利用率是 1/(12a)GBN 为什么丢一个包要重传一堆因为接收方丢弃了后续所有乱序分组SR 为什么快因为用接收缓存换来了只重传丢失分组。这三个问题答清楚评委基本不会再追问实现细节。整套源码、可编辑的实验报告文档和 README 运行说明都在资源包里按上面这份清单走一遍答辩环节基本不会卡壳。从那以后我每次做协议类课设都强制先画发送方和接收方的状态图再动代码这个习惯让我后面三次以为要重写的改动都变成了一次小修希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询