基于UDP的可靠传输:GBN/SW/SR协议源码与滑动窗口实现解析
发布时间:2026/10/1 10:35:00来源:尧图网络
简介一份基于Python实现的可靠数据传输协议实验/课程设计资源面向计算机网络课程学生与需要完成UDP可靠传输实验的开发者覆盖停等协议、GBN协议、SR协议三个层次的协议设计与实现。资源内含完整设计报告Word、Python源码及测试数据文件共14个文件以8个.py源码为主辅以3个txt数据文件、md说明、License等整体仅493KB适合直接阅读和运行验证。包内代码模块划分清晰包含server/client、protocolSW/GBN/SR以及config、util、device等辅助模块可帮助读者掌握基于UDP的单向/双向可靠数据传输、丢包模拟、文件传输C/S应用等关键点设计报告与源码结合便于对照原理、复现实验并完成课程报告。已有1014人学习/下载适合需要快速上手网络协议仿真与课程设计的同学。1. 可靠数据传输协议一份能跑通的 UDP 课程设计与三套协议源码如果你接触过计算机网络一定听过“UDP 不可靠”这句话可真要你在 UDP 之上实现一个可靠数据传输协议很多人第一反应是懵的套接字发出去就完事了怎么重传、怎么确认、怎么处理乱序教材里画得清楚代码里全是坑。这份资源就是冲着这个来的——它是一套完整的基于 Python 的可靠数据传输协议工程包含设计报告、可运行的 server/client 源码、GBN/SW/SR 三套协议实现以及发送和接收两端的实测数据文件。实验任务从最基础的停等协议起步逐步推进到 GBN 滑动窗口、SR 选择重传最后扩展成支持双向数据传输的 C/S 文件传输应用。适合正在做计网课程设计、需要验证滑动窗口机制、或者想抄一份完整协议代码来改的学生和从业者。2. 协议分层与代码包结构先看清 GBN、SW、SR 三个协议的边界2.1 从文件结构反推实验设计src 下每个模块在干什么拿到资源后先别急着跑先把目录结构理清楚。这份资源的文件组织方式是实验导向的根目录下有设计报告、数据目录、源码目录和接收结果目录按照“数据准备 → 协议实现 → 收发验证”三段组织。路径作用设计报告.docx完整实验报告包含停等/GBN/SR 的设计思路、验证数据data/client_data.txt客户端侧数据文件双向传输扩展时使用data/server_data.txt服务器侧发送的源数据文件recv/client_recv.txt客户端接收到的数据文件用于与源文件对比src/config.py全局参数端口、缓冲区、丢包率、窗口大小、序号空间src/util/device.py模拟网络层的丢包逻辑src/data.py数据拆包与重组负责把文件切分成定长载荷src/server.py服务端入口负责按协议发送数据src/client.py客户端入口负责接收并按序写入本地文件src/protocol/SW.py停等协议实现单包等待确认src/protocol/GBN.pyGBN后退 N 帧实现滑动窗口连续发送src/protocol/SR.pySR选择重传实现接收端缓存乱序包实验任务顺序是停等 → 验证丢包 → 改进为双向 → 升级为 GBN → 再升级为 SR。从文件结构能看出来protocol/目录把三种协议完全隔离server.py和client.py只是调用协议的入口这种解耦方式值得照抄因为后面你想单独验证某一个协议只需要改入口函数的引用。2.2 config.py 与 socket 二次封装UDP 收发的统一入口所有协议共用一个config.py这是我的习惯——把丢包率、超时、窗口大小全部参数化跑验证时只需要改数值不用动协议代码。参数含义如下# config.py协议参数与丢包模拟开关 HOST 127.0.0.1 # 本机回环地址便于课程设计验证 PORT 8888 # 服务器监听端口 BUFFER_SIZE 2048 # 单包载荷上限决定数据拆包粒度 LOSS_RATE 0.2 # 随机丢包率0 表示不丢包 TIMEOUT 1.0 # 超时重传等待时间单位秒 WINDOW_SIZE 8 # GBN / SR 公共窗口大小 MAX_SEQ 31 # 序号空间上限必须满足 2 * WINDOW_SIZEBUFFER_SIZE决定了文件被切成多大的载荷字符串“可靠传输协议测试数据”每个字符在 UTF-8 下可能占 3 字节所以 2048 并不等于能装 2048 个字符实际拆包时要按文件读取的字节数算。MAX_SEQ 31是经验值等于 32 个不同序号配合 8 的窗口大小能保证序号环绕后新旧分组可区分这个关系在避坑章节会专门展开。UDP socket 的收发本身只有sendto和recvfrom但可靠协议要求每一层都走统一的报文构造和解析避免server.py里直接操作 socket。常见做法是封装一对收发函数协议模块只负责拼报文底层统一处理字节序和超时# util/device.py 中的统一收发封装简化版 import socket import struct def send_unreliable(sock, packet, addr, loss_rate): 模拟不可靠链路按概率丢弃数据包 if loss_rate 0 and random.random() loss_rate: return False # 模拟丢包实际未发送 sock.sendto(packet, addr) return True def recv_with_timeout(sock, timeout): 带超时的接收超时返回 None 供上层触发重传 sock.settimeout(timeout) try: data, addr sock.recvfrom(2048) return data, addr except socket.timeout: return None, None这段代码的逻辑很直白send_unreliable在真正 sendto 之前按loss_rate丢弃一部分包模拟网络层的随机丢包recv_with_timeout把超时变成返回值这样协议层不需要异常处理就能判断“该重传了”。注意丢包一定要放在应用层协议之下也就是说 ACK 包同样会丢如果只丢数据不丢 ACK测出来的重传机制是假的。2.3 报文格式与丢包模拟ACK 和数据怎么区分三种协议共用一套报文格式这是整个资源里最值得复用的设计。报文用二进制结构体拼接头部包含序号、类型标志和校验段接收端先解析头部再决定走数据分支还是 ACK 分支# 报文格式seq(4字节) flag(1字节) payload(可变) # flag 含义0DATA 数据包1ACK 确认包 def build_packet(seq, flag, payloadb): header struct.pack(!IB, seq, flag) return header payload def parse_packet(packet): seq, flag struct.unpack(!IB, packet[:5]) return seq, flag, packet[5:]!IB分别是大端序的无符号整型和无符号字节网络字节序统一用!避免不同机器解析错位。seq是发送方分配的序号flag决定报文类型ACK 报文的 payload 为空只携带确认序号。这个格式虽然简单但足够支撑停等、GBN、SR 三种协议的演进因为三种协议的区别只在窗口管理和确认策略上报文本身不用换。3. 停等协议到 GBN把单包等待改成 8 格滑动窗口3.1 停等协议的吞吐瓶颈1 个包等一个 RTT停等协议的逻辑是最容易写的发送方发一个包等 ACK收到后再发下一个。它的正确性没问题但性能有硬伤——每一轮传输都要空等一个完整的 RTT。假设带宽 10 Mbit/s、RTT 30ms、包长 2048 字节链路利用率不到 3%剩下 97% 的时间都在等。这就是题目要求“改进停等协议”的原因。GBN 的改进思路是允许发送方在未收到 ACK 时连续发送多个包用两个指针描述窗口base是最老的未确认序号next_seq是下一个待发送序号。窗口大小是 8意味着最多允许 8 个包同时在链路上飞行。发送方一次把 8 个包全部塞进 socket然后等待 ACK 推进窗口这个推进过程就是协议的核心。3.2 GBN 发送方base/next_seq 与超时重发整个窗口GBN 发送方维护发送窗口缓冲区每个发出的包都存一份副本超时后把窗口内所有未确认的包重新发一遍。从src/protocol/GBN.py的结构看资源用的正是这个经典实现# protocol/GBN.py 发送方核心逻辑 class GBNSender: def __init__(self, sock, peer, window, max_seq): self.sock sock self.peer peer self.window window self.mod max_seq 1 self.base 0 # 窗口下沿最老的未确认序号 self.next_seq 0 # 窗口上沿下一个待发序号 self.packets {} # seq - 完整报文超时重传用 def send_one(self, payload): # 窗口已满等待 ACK 推进再继续发送 while (self.next_seq - self.base) self.window: self._wait_ack_loop() seq self.next_seq % self.mod packet build_packet(seqseq, flag0, payloadpayload) self.packets[seq] packet self.sock.sendto(packet, self.peer) self.next_seq 1 def _handle_ack(self, ack_seq): # 累积确认ack_seq 表示该序号之前的包都已正确到达 self.base max(self.base, (ack_seq 1) % self.mod) # 清理窗口内已确认的缓冲区 for seq in list(self.packets.keys()): if (seq - self.base) % self.mod self.window: continue self.packets.pop(seq, None) def _timeout_retransmit(self): # 超时重发 base 到 next_seq 之间所有未确认分组 for seq in range(self.base, self.next_seq): self.sock.sendto(self.packets[seq % self.mod], self.peer)这段代码的while轮询是简化写法真实工程里建议用select或threading.Timer做超时事件。(next_seq - base) window是窗口满的判据_handle_ack里对seq做模运算因为序号环绕后 31 后面是 0。_timeout_retransmit是 GBN 和停等的最大区别停等只重发一个包GBN 重发整个窗口——因为接收方只按序接收窗口里一旦有包丢失后续所有到达的包都会被丢弃重发后续包没有意义。3.3 GBN 接收方累积确认与乱序丢弃GBN 的接收方极其“挑剔”它只接受恰好等于期望序号的包其他全部丢弃并回传最后一个正确接收的 ACK。这样做的目的是告诉发送方“我已经确认到这个位置再往后的都白发了”。# protocol/GBN.py 接收方核心逻辑 def gbn_receive(sock, expected_seq, max_seq): packet, addr sock.recvfrom(2048) seq, flag, payload parse_packet(packet) if flag 1: # ACK 包交给上层处理 return expected_seq, None, seq if seq expected_seq: # 数据包按序到达确认并推进 ack build_packet(seqexpected_seq, flag1) sock.sendto(ack, addr) expected_seq (expected_seq 1) % (max_seq 1) return expected_seq, payload, None else: # 乱序包直接丢弃重发累计确认 ack build_packet(seq(expected_seq - 1) % (max_seq 1), flag1) sock.sendto(ack, addr) return expected_seq, None, None注意 ACK 携带的序号不是“刚收到的包的序号”而是“期望下一个收到的序号”。当收到 seq5 但期望是 6 时回传的 ACK 序号是 5表示“5 及 5 之前的都收到了给我发 6”。这个累积确认语义是 GBN 的识别特征到了 SR 协议会被彻底替换成选择性确认。4. GBN 改进为 SR接收端缓存乱序分组的编排方法4.1 GBN 的重复重传代价与 SR 的窗口限制GBN 在丢包率低的时候表现很好因为偶尔丢一个包重发整个窗口的成本可以接受。但 WAN 环境丢包率一旦超过 1%GBN 的吞吐会断崖式下跌最坏情况下每个窗口都要整窗重发。SR 协议Selective Repeat选择重传把“重发整个窗口”改成“只重发缺失的那一个包”接收端需要把乱序到达的包缓存下来等缺失包补齐后再整体交给上层。教材里反复强调 SR 的约束发送窗口和接收窗口的大小不能超过序号空间的一半。原因在于序号空间有限如果窗口太大接收方无法区分一个到达的序号是新包还是迟到的重传包。这份资源里MAX_SEQ 31、WINDOW_SIZE 8正好满足MAX_SEQ 1 2 * WINDOW_SIZE也就是 32 大于等于 16留了一倍冗余。4.2 SR 发送方独立计时器与选择性重传SR 发送方不再“一超时全重发”而是为每个在窗分组维护独立的超时状态。收到某个分组的 ACK 后只移除该分组的缓冲区窗口推进的条件是base指向的分组已经被确认。# protocol/SR.py 发送方核心逻辑 class SRSender: def __init__(self, sock, peer, window, max_seq, timeout): self.sock sock self.peer peer self.window window self.mod max_seq 1 self.base 0 self.next_seq 0 self.packets {} # seq - (packet, sent_time) self.timeout timeout def _handle_ack(self, ack_seq): # 选择性确认只移除 ack_seq 对应的分组 if ack_seq in self.packets: del self.packets[ack_seq] # 推进 base从当前 base 开始连续确认过的序号 while self.base not in self.packets and self.base ! self.next_seq: self.base (self.base 1) % self.mod def _check_timeout(self): # 每个超时的分组单独重发不影响其他分组 now time.time() for seq, (packet, sent_time) in list(self.packets.items()): if now - sent_time self.timeout: self.sock.sendto(packet, self.peer) self.packets[seq] (packet, now)和 GBN 的_handle_ack对比差别在确认粒度GBN 收到 ACK5 会把 base 直接推进到 6SR 收到 ACK5 只移除 5如果 4 还没被确认窗口还是不会动。_check_timeout里的字典遍历是逐包检查真实实现里可以用最小堆或红黑树管理超时时间避免每次全量扫描。4.3 SR 接收方缓存去重与按序交付SR 的接收方比 GBN 复杂得多它要维护一个接收窗口、一张缓存表和rcv_base指针。合法到达的分组落在窗口内才缓存窗口外直接丢弃缓存命中后从rcv_base开始连续交付给上层文件写入模块。# protocol/SR.py 接收方核心逻辑 class SRReceiver: def __init__(self, window, max_seq): self.window window self.mod max_seq 1 self.rcv_base 0 # 接收窗口下沿 self.cache {} # seq - payload def accept(self, seq, payload, addr, sock): # 去重窗口内已经缓存过的分组直接忽略 if seq in self.cache: return [] distance (seq - self.rcv_base) % self.mod if 0 distance self.window: self.cache[seq] payload ack build_packet(seqseq, flag1) sock.sendto(ack, addr) # 从 rcv_base 开始连续取出可交付的分组 delivered [] while self.rcv_base in self.cache: delivered.append(self.cache.pop(self.rcv_base)) self.rcv_base (self.rcv_base 1) % self.mod return delivered这段代码的distance (seq - self.rcv_base) % self.mod是判断 seq 是否落在接收窗口内的标准写法窗口内条件0 distance self.window保证了对环形序号空间的处理。注意到 ACK 回传的是当前收到的分组本身的 seq而不是 GBN 里的“期望序号”这正是选择性确认的核心。delivered返回的是按序排列的数据块列表上层可以安全地按顺序写文件了。5. 避坑与常见问题丢包率、序号空间与文件写入脏数据5.1 序号空间死锁窗口边界判断错误导致协议假死现象大文件传输过半后server 端疯狂重传client 端不再回复 ACK日志里重复出现相同 seq最后整个传输卡死。原因MAX_SEQ设置太小窗口大小和序号空间的比例失衡。比如MAX_SEQ7、WINDOW_SIZE8序号空间只有 8 个数窗口也是 8序号环绕之后接收方完全无法判断收到的是新分组还是超时重传的旧分组于是去重和确认逻辑全部错乱。解决序号空间必须不小于窗口大小的两倍。GBN 和 SR 都要求MAX_SEQ 1 2 * WINDOW_SIZE直接用资源里的MAX_SEQ31配WINDOW_SIZE8就没问题。我一般会把 MAX_SEQ 写成2 * WINDOW_SIZE * 2 - 1留更多余量。5.2 固定超时导致吞吐崩盘丢包率一高就重传风暴现象LOSS_RATE0.1时一切正常调到 0.3 后收到大量重复 ACK重传次数几十倍上涨吞吐接近归零。原因超时时间固定为 1 秒丢包率升高后实际 RTT 波动加大重传分组本身也被丢弃发送方等不到 ACK只能继续叠加重传。固定超时没有感知链路实时状态重传风暴不可避免。解决做一个简单的 RTO 估算每次收到 ACK 后更新超时时间。常见做法是加权平均rto (1 - alpha) * rto alpha * rttalpha 取 0.125超时触发后把 rto 乘 1.5 做指数退避避免同一包的多次重传互相碰撞。5.3 重复分组写入脏数据文件比源文件大尾部多出一截现象接收端收到的数据总量超过源文件大小client_recv.txt和server_data.txt对比时尾部多出重复内容。原因ACK 丢失触发重传接收方收到重复分组。GBN 接收方因为只接受期望序号天然完成去重但换成 SR 后缓存表虽然去重了上层文件写入逻辑如果没有按“交付顺序”写而是遇到数据就追写重复交付的数据就从缓存里漏进了文件。解决文件写入必须和协议交付挂钩。SR 接收方的accept返回delivered列表上层只对这个列表里的数据做顺序写入缓存的乱序包不落盘。另外可以在文件写入时按 seq 映射偏移offset seq * BUFFER_SIZE这样即使重复写入覆盖的是同一个位置不会让文件变大。5.4 丢包模拟放在错误的位置丢包率设置后测不出任何重传现象把LOSS_RATE1理论上全部丢包但运行结果却是一切正常文件完整接收重传次数为零。原因丢包逻辑被写在了协议层之上的业务函数里比如 server 发完数据后自己造了个“发送成功”的日志真正的sendto从来没有链路层的丢包。这种错误最常见的原因是直接在server.py里做随机丢弃而util/device.py的丢包函数没有被调用。解决丢包必须放在统一收发函数里并且要影响两个方向——数据包和 ACK 包都要经过丢包判断。用send_unreliable替换所有直接sendto的调用点。验证方法是把LOSS_RATE调成 0.3 跑一次统计重传次数如果不为 0 说明丢包通路生效。5.5 双向传输时 ACK 和数据互相污染现象按题目要求把停等协议改成双向传输后一个方向数据全部超时另一个方向收到的数据出现乱码。原因双向传输共用一个 socket接收循环里没有区分包类型。数据包和 ACK 包到达后都被当成数据处理ACK 的代码被写进文件。解决接收后先解析 flagflag1走 ACK 处理分支flag0才进入数据交付。不要靠“包长度”判断类型因为载荷长度可能为 0也会和 ACK 混淆。按照parse_packet返回的三元组做分支是最可靠的。6. 验证协议正确性丢包梯度对照与文件一致性校验6.1 回归验证流程先跑零丢包基线再跑梯度丢包率协议改完第一步不是调参数而是先验证正确性。我习惯把验证流程固定成三步先把LOSS_RATE改成 0 跑一遍确认无丢包环境下三种协议都能完整传输再逐步把丢包率调成 0.1、0.2、0.3观察重传次数是否随之上升最后对比收发文件的哈希值只有字节级一致才算通过。# 第一步零丢包基线验证 python src/server.py python src/client.py # 第二步对比源文件与接收文件的一致性 python -c import hashlib for path in [data/server_data.txt, recv/client_recv.txt]: h hashlib.sha256(open(path, rb).read()).hexdigest() print(path, h) 跑通后把config.py里的LOSS_RATE改成 0.2 重跑一遍如果协议实现正确传输耗时和重传次数会明显上升但文件哈希必须一致。这一步能同时验证丢包模拟是否生效、重传机制是否工作、去重和按序交付是否正确。记住丢包率改到 0.3 以上时一定要同步检查有没有触发序号环绕问题这也是我坚持用MAX_SEQ 31配WINDOW_SIZE 8的原因。6.2 把设计报告和实测数据做成对照表课程设计答辩时最怕的是“代码能跑但说不出验证过程”。资源里的设计报告.docx提供了报告框架我建议在报告里补一张三协议对照表停等/GBN/SR 在相同丢包率下的重传次数、传输耗时、文件哈希是否一致。数据不用多0、0.1、0.2 三档足够证明协议行为。实际测试下来GBN 在低丢包率下超时重传次数略高于 SR但接收方逻辑简单SR 在高丢包率下重传包数明显更少代价是缓存管理和交付逻辑复杂度翻倍。从那以后我每次改协议都强制自己先跑一遍零丢包基线再跑梯度丢包最后比对哈希。这套流程救过我很多次尤其是从 GBN 改成 SR 那次缓存去重逻辑折腾了一晚上最后就是因为漏了基线测试把 bug 归到了丢包上。希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网