资讯详情 基于Python实现可靠数据传输协议:从停等到滑动窗口的完整课设指南
📅 2026/10/19 22:04:03
简介这是计算机网络课程设计场景下的一份Python实现资源包围绕UDP设计可靠数据传输协议完整覆盖停等协议、GBN和SR的改进思路。内容对应实验要求包括单向可靠传输、丢包模拟、双向数据传输以及基于协议的C/S文件传输应用适合正在学习网络原理或需要完成课程设计参考的读者。压缩包共14个文件约723KB以8个Python源文件为核心配合txt数据文件、docx实验报告、README说明和License文件既有可运行的server与client源码也有配置工具和数据样本。已有596人学习下载作者s1t16按实验步骤组织文件便于从基础停等协议逐级扩展到GBN与SR。参考README和报告可快速理解协议状态机、滑动窗口与丢包重传机制也可将其中的UDP可靠性扩展思路迁移到文件传输场景适合实践入门或二次开发。1. 从 UDP 不可靠说起这门课设到底要你交出什么很多同学拿到“基于 Python 实现可靠数据传输协议”这个题目第一反应是“直接用 TCP 不就行了”。但如果真这么做课程设计基本就翻车了。题目要求的是基于 UDP 设计并实现停等协议、GBN 协议再演进到 SR 协议整个实验的核心不是“传数据”而是让你在 UDP 这种不可靠、不保序、无拥塞控制的信道上通过协议机制去解决丢包、重复、乱序的问题。换句话说你需要亲手写出一套“迷你 TCP”这才是这份资源真正的价值所在。这份编号为 100010493 的课程设计资源覆盖了从停等协议到 GBN、再到 SR 的完整演进链路并且包含服务器到客户端的单向传输、双向数据传输模拟、基于停等协议的 C/S 文件传输应用等全部实验内容。它的设计目标很明确让你从零开始理解可靠数据传输的基本原理掌握滑动窗口协议的工作原理并且能直接用 Python 代码验证自己的思路。无论你是计算机网络课程需要交课设报告还是想深入理解 TCP 可靠传输背后的设计哲学这套方案都能作为你直接参考甚至照搬的完整蓝本。接下来我会按照实际做项目的顺序从总体设计、模块实现、避坑指南到最终验收把这份资源里的关键技术点和工程实现细节全部拆给你看。2. 总体架构设计协议分层与四个核心模块的职责划分做网络协议这类项目最忌讳一上来就写代码。我一般会先把架构图在脑子里画清楚数据从服务器端产生经过协议层封装通过 UDP Socket 发送再在客户端经过对应的协议层解封装最后交给应用层处理。这份资源里的源码组织方式非常标准适合直接作为课设报告中的“系统设计”章节素材。2.1 工程文件结构与启动入口解析拿到压缩包后首先需要读懂它的目录结构这决定了你能否快速跑通整个项目。标准的资源解压后通常是这样的结构datatransferprotocol/ ├── src/ │ ├── protocol/ │ │ ├── __init__.py │ │ ├── server.py │ │ ├── client.py │ │ └── util.py │ ├── config.py │ └── data/ │ ├── client_data.txt │ └── server_data.txt ├── recv/ │ └── client_recv.txt ├── report.docx ├── README.md └── LICENSE这个结构划分非常清晰。src/protocol/下是协议栈的核心实现其中server.py负责发送端逻辑client.py负责接收端逻辑util.py通常封装了校验和计算、数据包序列化等公共工具函数。config.py是全局配置项比如 IP 地址、端口号、窗口大小、超时重传时间等参数都定义在这里。data/目录存放测试数据文件recv/目录存放客户端接收到的数据。整体是标准的 C/S 架构非常适合作为计算机网络课程设计的演示项目。2.2 数据包格式设计与序列化方法可靠传输协议的基石是数据包的格式设计这和 HTTP 协议头、TCP 报文段格式是一样的道理。在这个项目里每个数据包都必须包含几个关键字段序列号Sequence Number、确认号Acknowledgment Number、校验和Checksum、数据长度Length以及实际数据负载。用 Python 实现时struct模块是处理二进制序列化最方便的工具。import struct # 数据包格式: [序列号(4字节) | 确认号(4字节) | 校验和(2字节) | 长度(2字节) | 载荷] PACKET_HEADER_FORMAT !IIHH HEADER_SIZE struct.calcsize(PACKET_HEADER_FORMAT) MAX_PAYLOAD_SIZE 1024 # 最大载荷 1KB def make_packet(seq_num: int, ack_num: int, payload: bytes b) - bytes: length len(payload) # 计算校验和: 先将头部和载荷拼在一起对每个16位字求和 header_no_checksum struct.pack(PACKET_HEADER_FORMAT, seq_num, ack_num, 0, length) checksum compute_checksum(header_no_checksum payload) header struct.pack(PACKET_HEADER_FORMAT, seq_num, ack_num, checksum, length) return header payload def parse_packet(packet: bytes): header packet[:HEADER_SIZE] seq_num, ack_num, checksum, length struct.unpack(PACKET_HEADER_FORMAT, header) payload packet[HEADER_SIZE:HEADER_SIZE length] return { seq_num: seq_num, ack_num: ack_num, checksum: checksum, length: length, payload: payload, }注意这里的校验和计算类似 TCP 的校验和算法将整个数据包包括头部和载荷按 16 位一组进行反码求和。接收端收到数据包后重新计算校验和如果与包内携带的校验和不一致就说明数据在传输过程中被破坏了必须丢弃并请求重传。使用!IIHH格式化串表示使用网络字节序大端序避免不同主机架构间的字节序问题。2.3 滑动窗口协议的状态变量设计与职责边界从停等协议演进到 GBN 协议核心在于引入滑动窗口窗口大小决定了信道利用率。停等协议的利用率太低了发一个包等一个确认往返时间(RTT)内发送方完全空闲。如果链路带宽高、延迟大停等协议的信道利用率会趋近于零。GBN 协议通过允许发送方连续发送多个未被确认的分组把窗口“滑”起来从而大幅度提升吞吐量。在这份资源的 GBN 实现中发送端需要维护以下状态变量base窗口基序号、next_seq_num下一个要发送的序号、window_size窗口大小、timer计时器。接收端则维护expected_seq_num期望接收到的序号。整体设计上protocol/server.py负责发送窗口管理和超时重传逻辑protocol/client.py负责接收端的累积确认和丢包检测逻辑两个模块通过 UDP Socket 通信职责边界非常清晰。3. 协议演进实现从停等机制到滑动窗口的工作原理中间这章是整个课设的核心和价值所在。你需要向答辩老师展示的不仅是能跑通的代码更是你对这几种协议机制差异的深刻理解。3.1 停等协议设计超时重传与确认机制停等协议是最基础的可*靠数据传输协议原理非常简单发送方发送一个分组后必须等待接收方返回确认(ACK)收到确认后才能发送下一个分组。如果发送方在超时时间内没有收到 ACK就重传该分组。def stop_and_wait_send(sock, dest_addr, payload, timeout2.0): packet make_packet(seq_num0, ack_num0, payloadpayload) while True: sock.sendto(packet, dest_addr) sock.settimeout(timeout) try: response, _ sock.recvfrom(HEADER_SIZE) ack_packet parse_packet(response) if ack_packet[ack_num] 0: print(f[发送端] 收到对 seq0 的确认发送完成) return True except socket.timeout: # 血泪经验超时后必须重新发送并且要重新设置计时器 print(f[发送端] 超时未收到ACK重传 seq0) continue这个实现里的关键点有两个。第一个是超时重传很多同学在这里踩坑单程超时时间设置得太短导致频繁重传设置得太长又会大幅降低传输效率。我一般建议超时时间设置为略大于往返时间的两倍。第二个是 ACK 确认号的设计停等协议中确认号的含义是“我已经正确收到序号为 N 的数据包下一个期望收到的是 N1”。3.2 累积确认、超时重传与窗口滑动把停等协议的等待时间“填满”就得到了 GBN 协议的核心机制发送窗口。发送方维护一个窗口窗口内所有分组都可以连续发送而不用等待各自的 ACK。接收方只按序接收一旦收到乱序分组就直接丢弃并对最后一个按序接收到的分组发送累积确认。GBN 发送端的核心逻辑是判断哪些分组可以发送哪些分组必须等 ACKdef gbn_send(sock, dest_addr, data_packets): 按 GBN 协议发送一组数据包。 :param data_packets: 已按序号排好的数据包列表 window_size config.WINDOW_SIZE base 0 # 窗口起始序号(绝对序号) next_seq_num 0 # 下一个待发送的序号 total_packets len(data_packets) while base total_packets: # 发送窗口还没满且还有包可发就继续发包 while next_seq_num base window_size and next_seq_num total_packets: sock.sendto(data_packets[next_seq_num], dest_addr) print(f[发送端] 发送 seq{next_seq_num}) next_seq_num 1 # 启动一个计时窗口等待累积确认 sock.settimeout(config.TIMEOUT) try: response, _ sock.recvfrom(MAX_PACKET_SIZE) ack_packet parse_packet(response) # 累积确认收到对 n 的确认意味着前面 n 个包全部正确到达 new_base ack_packet[ack_num] 1 if new_base base: print(f[发送端] 收到累积确认 ACK{ack_packet[ack_num]}窗口滑到 {new_base}) base new_base except socket.timeout: # 超时从 base 开始重传整个窗口 print(f[发送端] 超时从 seq{base} 开始重传整个窗口) next_seq_num base continue这里最容易理解错的是累积确认。比如接收方已经按序接收了 0、1、2那么发送 ACK 中的确认号为 2表示“0~2 全部收到请发 3”。如果发送方收到 ACK2窗口基序号直接变成 3窗口向前滑动 3 个位置。如果第三个包丢失了接收方收到第四个包时发现序号不连续会直接丢弃并重复发送 ACK2发送方重复收到同一个 ACK达到一定次数后触发快速重传或超时重传。3.3 GBN 改进为 SR 协议接收窗口与选择重传GBN 协议的致命缺点是单个丢包会导致大量重传。比如窗口大小是 16第 4 个包丢了接收方会丢弃所有后续按序到达的包发送方超时后必须重传 5~16 号包。在误码率高的信道上这种“回退 N”策略的吞吐量会受到明显影响。SR选择性重传协议就是针对这个问题做的优化。SR 不仅为发送方提供滑动窗口也为接收方提供滑动窗口缓存机制。接收方不再丢弃乱序到达的分组而是先缓存起来等缺失的分组到达后再一起按序交付给上层。def sr_receive(sock, base, window_size, buffer_dict): SR 接收端处理逻辑。 :param buffer_dict: 序号-包内容的缓存字典 while True: packet_data, addr sock.recvfrom(MAX_PACKET_SIZE) pkt parse_packet(packet_data) seq pkt[seq_num] # 接收窗口范围判定 if base seq base window_size: buffer_dict[seq] pkt # 先缓存起来存着 # 移动窗口如果当前 base 指向的数据已经到达就持续前移 while base in buffer_dict: # 将缓存中的数据按序交付给上层应用 deliver_to_app(buffer_dict[base][payload]) del buffer_dict[base] base 1 send_ack(seq) # 对已收到的包确认注意这里是选择确认 elif seq base: # 说明这个包是重复的旧包接收方已经滑过去了但还是得再发一次ACK send_ack(seq)SR 与 GBN 的差异在实现中非常关键。GBN 的 ACK 是“累积”的SR 的 ACK 是“选择”的。SR 接收端需要为没有到达的分组设置定时器或者在发送端单独计时重传每个超时分组的 ACK。从代码量来看SR 的实现复杂度比 GBN 高许多。这也是实验内容的最后一个进阶步骤将所设计的 GBN 协议改进为 SR 协议。如果你做到这一步说明你已经完全掌握了滑动窗口协议的精髓完全可以作为课设报告里的“创新点”来写。4. 工程落地实现单向传输、双向传输与文件传输应用协议机制的代码是骨架工程落地则是血肉。课设要求里明确提到了三个必须实现的场景单向可靠数据传输、模拟数据包丢失验证协议有效性、双向数据传输以及一个 C/S 结构的文件传输应用。4.1 基于 UDP 的单向可靠传输测试链路先从最基本的单向传输链路说起。服务器端按顺序读取文本数据切分成固定大小的数据块用 GBN 协议发送给客户端客户端接收后重组数据写回本地文件。整个链路的测试步骤如下先跑通“无丢包”场景修改config.py中的丢包率配置为 0验证基础传输功能是否正常。再跑通“模拟丢包”场景把丢包率设为 10%~30%观察协议在随机丢包环境下是否仍能保证数据完整交付。模拟丢包时注意util.py里通常有一个随机数丢失判断。这个判断会直接改变实验结果def should_drop_packet(drop_rate: float 0.0) - bool: 按丢包率决定是否丢弃当前数据包 return random.random() drop_rate def udt_send(sock, packet, dest_addr, drop_rate0.0): 不可靠信道传输按概率丢包 if should_drop_packet(drop_rate): print(f[信道] 数据包 seq{parse_packet(packet)[seq_num]} 在传输中丢失...) return sock.sendto(packet, dest_addr)一定要理解这段代码的位置关系。丢包模拟发生在“发送端调用 sendto 之前”模拟的是数据在网络上传输时被路由器丢弃的过程。实际实验中可以通过打印日志对比相同序号数据包的出现次数来证明重传机制确实触发了。这也是报告里“实验结果与分析”部分的关键截图依据。4.2 双向数据传输的实现方式与冲突避免很多同学在把单向传输改成双向传输时犯了难。双向传输并不是简单地开两个 Socket 各收各的而是要在一个事件循环里同时处理“发送数据”和“接收确认”两条逻辑流。如果处理不当就会出现死锁双方都在等对方的 ACK但双方的 ACK 都发送不出去。实现双向传输我一般会采用单 Socket 状态切换的策略把收发逻辑合并到同一个循环里在每个循环周期内先检查有没有需要重传的超时分组再尝试接收对端的数据和确认最后发送新数据。通过 socketselect或设置settimeout来避免阻塞def bidirectional_send_receive(sock, dest_addr, local_data): 在同一线程内交替处理发送和接收避免双向传输死锁 base 0 next_seq_num 0 while base len(local_data) or has_received_data: # 先从窗口里往外发数据 while next_seq_num base config.WINDOW_SIZE and next_seq_num len(local_data): sock.sendto(make_packet(next_seq_num, 0, local_data[next_seq_num]), dest_addr) next_seq_num 1 # 再尝试接收对方的数据/确认 sock.settimeout(0.5) # 非阻塞式检查停顿 500ms try: packet sock.recvfrom(MAX_PACKET_SIZE)[0] handle_packet_type(packet) # 根据类型分发到数据/确认处理逻辑 except socket.timeout: pass # 处理下一批数据 # 最后检查是否超时需要重传 if timer_expired(): next_seq_num base这里有个非常实用的细节如果两端的发送节奏不是对等的比如服务器要发大文件而客户端只返回少量确认那么客户端的发送缓冲区会很快清空。此时客户端不能退出循环必须继续处理接收到的数据包并发送 ACK。正确做法是客户端在自己的循环里等到“收到服务器的传输结束标志”或“超时退出”后再关闭 Socket。这是课设答辩时老师最喜欢追问的细节之一。4.3 基于停等协议的 C/S 文件传输应用实现文件传输应用是停等协议最直观的应用场景。实现逻辑是服务器读取文件内容、按 chunk 分割、加序号封装成包客户端收包后按序号重组并写入新文件。这个场景和纯文本传输的区别在于它必须考虑到大文件的处理效率和异常恢复能力。def send_file(sock, file_path, dest_addr, chunk_size1024): 基于停等协议发送文件 :return: 成功发送的字节数 seq 0 total_bytes 0 with open(file_path, rb) as f: while True: chunk f.read(chunk_size) if not chunk: break packet make_packet(seq_numseq, ack_num0, payloadchunk) stop_and_wait_send(sock, dest_addr, packet) seq 1 total_bytes len(chunk) print(f[文件传输] 完成共发送 {seq} 个数据包{total_bytes} 字节) return total_bytes文件传输的实现要注意几个问题。每次只发送 1024 字节如果文件有几十 MB就需要发送几万个数据包停等协议的效率会非常低。这也是为什么课设要求里先做停等协议的文件传输再在 GBN 基础上做同样的文件传输时你会明显感受到性能差异GBN 的吞吐量可能是停等协议的几十倍甚至上百倍。这个对比数据写到课设报告的“实验结果分析”里是非常有说服力的论据。5. 避坑指南协议实现中五个最容易翻车的故障排查计算机网络课设的调试过程往往是玄学般的体验。代码写好了、网络配置也对但数据就是传不过去。基于这份资源里协议的实现经验我整理了五个高频故障点按照现象、原因、解决的思路来排查能帮你少走很多弯路。5.1 重传日志打了一堆但接收端还是收不全数据现象服务器端疯狂打印“超时重传”日志客户端却始终只能收到前几个数据包后面全部丢失。原因这是典型的“发送窗口推进”逻辑错误。在很多初版 GBN 实现里发送方在超时后直接把next_seq_num重置为base但此时窗口内可能还有之前未发送完的数据包。如果重传后没有正确更新base窗口就永远滑不动形成死循环。解决逐行检查窗口滑动的状态转移。在超时重传后必须重新发送从base到next_seq_num的所有数据包并且每次收到 ACK 后base的更新逻辑必须严格等于“ACK序号 1”。我建议在每次状态变化时打印一行包含base、next_seq_num、window_size的日志辅助判断问题所在。5.2 接收端收到的数据完整但顺序错乱文件打不开现象客户端收到的数据量是对的但重组后的文件与原始文件不一致或者图片文件直接无法打开。原因数据包乱序。UDP 本身不保证数据包的到达顺序虽然 GBN 接收端会丢弃乱序包但如果你错误地实现了“直接收下所有包”的逻辑就会出现乱序交付。解决检查接收端是否严格按“期望序号”进行过滤。GBN 接收端唯一的合法交付依据是if seq_num expected_seq_num:只有序号完全匹配才接收并交付上层否则直接丢弃。SR 接收端允许缓存乱序包但在交付给应用层时也必须按缓存中的序号从小到大依次交付序号中间有空洞时绝不能提前交付后面的包。5.3 开启模拟丢包后程序运行了几分钟还没有结束现象丢包率设置为 20% 后程序就像卡死了一样日志长时间不更新。原因不是程序死锁而是超时时间设置太长。假设你的 RTT 是 100ms但超时时间设了 10 秒。每遇到一个丢包发送方就要干等 10 秒才重传传输效率断崖式下降。解决把超时时间调到理论 RTT 的两倍左右。在局域网环境中一般 0.5~1 秒足够充分。如果不知道网络 RTT可以先用ping命令粗测再在config.py里动态配置。更高级的做法是实现自适应重传根据最近的 RTT 样本计算加权平均 RTT 和偏差设置动态超时时间这也是报告里可以展开写的优化点。5.4 双向传输时两边都在等 ACK程序死锁现象开启双向传输功能后程序要么直接卡死要么等待很长时间后才开始传输。原因这是典型的“全双工通信中的死锁问题”。如果发送方在等待 ACK 时使用阻塞式recvfrom且接收方也在等待对方的数据才发送 ACK比如双方都尝试先发送数据再接收就会形成循环等待。解决采用事件循环或select机制处理 Socket。在等待超时时间内同时监听“可读事件”和“可写事件”有数据可读就先读有 ACK 需要确认就先处理 ACK。关注点转移把“发送-等待”的线性模型改成“发送-监听-处理”的并发模型这是工程实现里非常重要的思维转变。5.5 校验和算不对接收端把所有包都视为损坏现象接收端报错丢弃所有数据包。调试发现明明在同一个机器上收发校验和计算方式没有问题但解析出的数据和发送前不一致。原因字节序问题或者是没把校验和字段本身置零。计算校验和时头部中的checksum字段必须为 0计算完再填入接收端解析时要先取校验和字段将它置为 0 后重新计算整体校验和与取出值比较。解决严格顺序是“置零 → 拼接 → 计算 → 填充收到时 取校验和 → 置零 → 拼接 → 重算 → 对比”。此外如果是在 Windows 和 Linux 主机间跨平台传输要注意 sockaddr 结构和字节序的差异。6. 验收与答辩准备如何把实验结果讲清楚、拿高分做课程设计最后一步就是写报告和现场演示了。很多同学程序能跑但讲不清楚原理答辩时被老师追问几句就答不上来白白丢了分。这一章我重点讲验收的方法论和答辩时的准备策略。6.1 制定可量化的验收标准清单验收之前先明确“什么算完成什么算优秀”。我会建议你用下面这个表格来逐项自查验收项合格标准优秀标准停等协议在无丢包网络下能正确传输 1KB 文本接收端数据与发送端一致在 10% 丢包率下仍能正确传输且日志清晰展示重传过程GBN 协议在模拟丢包环境下传输数据保证 100% 完整性用两组不同丢包率如 5% 和 30%做对比实验计算出信道利用率的变化趋势双向数据传输两个方向的数据都能正确到达对方双方同时发送大数据量时不会死锁传输完毕后正确关闭连接文件传输应用传输一个 1MB 文件MD5 校验一致对比停等协议和 GBN 协议传输同一文件的总耗时给出统计图表SR 协议改进能正确实现选择重传乱序到达的分组能被缓存并最终按序交付与 GBN 协议在相同丢包率下对比“重传数据包数量”SR 明显小于 GBN把这张表里的每一项都实际跑通一遍你的报告里的“实验结果”章节就有血有肉了。6.2 答辩演示脚本的编排技巧答辩的现场演示时间是有限的通常只有 5~10 分钟。不要现场去敲代码或临时改配置。提前准备一个 5 分钟的演示脚本强烈建议按以下流程编排第一步先花半分钟展示项目结构快速过一眼代码组织证明这是你的自主实现。第二步启动停等协议传输一个文本文件全程无丢包速度很慢展示成功结果。第三步开启 20% 丢包率展示协议重传流程和接收结果。第四步启动 GBN 协议传输大文件展示窗口滑动的高吞吐量对比第二步明显提速。第五步展示 SR 协议的重传统计对比 GBN 的重传数量说明自己理解了改进动机。演示中最容易翻车的点是“开着丢包率但程序崩溃了”。为了保险起见我会把丢包率和丢包种子随机种子配置成可复现的固定值确保演示过程每次都能重现相同的结果而不是依赖于不可控的随机数。6.3 答辩追问环节的应答框架与经验教训答辩老师几乎必问的几个问题我帮你把答法提前准备好。第一个问题为什么信道不可靠但还要基于 UDP 设计可靠协议回答核心是TCP 的可靠传输是在内核协议栈里的看不到隐藏的实现细节基于 UDP 自己实现能理解和控制每一个机制这才是协议设计的本质。第二个问题GBN 为什么叫回退 N回退的是哪 N 个你要能画出时序图指出丢失的包 N 以及其后的所有数据包都需要重传。第三个问题SR 协议是怎么减少无效重传的答案是接收端缓存窗口 发送端单独定时器。关于资源里的代码如何避免被质疑雷同我有个实际的经验不要原封不动地提交代码。哪怕只改动一些变量命名、增加注释、调整函数拆分的粒度都能体现你对代码的理解。更推荐的做法是基于资源里的框架自己重新实现一遍核心的窗口管理逻辑遇到问题再来对照参考源码。从那以后我每次做协议类项目都会给自己定一条规矩交给验收的东西一定是在我脑子里完整过过一遍流程的能画得出时序图能解释得清状态变化绝不交一份“能跑但讲不明白”的代码上去。课设和真实项目都是一样能清晰表达出来的设计才算真正掌握。希望这份基于 Python 实现可靠数据传输协议的完整拆解能帮你在课程设计和毕业设计中少踩几个坑。祝做出一个能让你站上讲台自信讲演的优秀作品。本文还有配套的精品资源点击获取