ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

纯Python实现GBN与停等协议:从socket到滑动窗口

纯Python实现GBN与停等协议:从socket到滑动窗口 简介本资源是一份面向计算机网络课程设计与协议实践学习者的Python编程实战项目聚焦UDP底层可靠传输机制的原理理解与工程实现。内容覆盖停等协议与GBN协议的设计、双向扩展及向SR协议的演进完整支撑课程实验中关于丢包模拟、ACK确认、超时重传等核心环节的验证需求。压缩包共14个文件含8个Python源码server.py/client.py/protocol/等模块化实现、3个测试数据文本、1份Word实验报告、1份Markdown说明文档及LICENSE许可文件723KB体积轻量易部署。已有595人学习下载资源结构清晰、注释充分提供从协议基础逻辑到C/S文件传输应用的完整代码链路特别适合网络编程初学者通过可运行示例深入掌握可靠传输协议的实现细节与调试方法。1. 这不是“又一个UDP练习”它用纯Python把GBN和停等协议焊进真实socket层连丢包重传的超时抖动都可调你写过socket.sendto()但没亲手捏过ACK序号校验逻辑你跑过python -m http.server却没在server.py里见过一个字节一个字节拼接滑动窗口状态机你调试过ConnectionResetError但没在client.py里为第3次重传失败手动触发回退到停等模式——这个编号100010493的课程设计就是把教科书里“GBN窗口大小4”这种抽象描述直接编译成可打断点、可改config.py里TIMEOUT_MS 2000、可注入loss_rate0.3来压测的黑匣子。它不依赖任何第三方网络库零scapy、零twisted只靠原生socketthreadingstruct把可靠传输的三大核心序号管理、ACK确认、超时重传全摊开在src/protocol/目录下。适合正在啃《计算机网络自顶向下》第三章、手写TCP简易版卡在重传计时器的同学也适合想给毕设加个“自研轻量级可靠传输模块”的嵌入式方向开发者。它不是玩具是能让你在Wireshark里看到自己构造的SEQ5, ACK3, WINDOW4UDP载荷的真实协议栈。2. 从server.py启动那一刻起理解协议分层与状态机如何驱动每一次sendto()2.1 协议栈分层为什么protocol/目录下要拆出StopAndWait.py和GBN.py这个项目没用类继承搞“Protocol基类”而是用文件级隔离强制区分协议行为。src/protocol/StopAndWait.py里只有两个核心函数send_packet()负责封装SEQDATACHECKSUMrecv_ack()阻塞等待单个ACK并校验。而GBN.py则多出三个关键结构window_base: 当前窗口最左边界序号如0next_seq_num: 下一个待发序号如4窗口大小4sent_packets: 字典缓存{seq: (data, timestamp)}用于超时重传提示GBN.py中send_packet()会检查next_seq_num window_base WINDOW_SIZE才发包否则直接return——这就是滑动窗口的“流量控制”本质不是靠sleep是靠序号比较。2.2server.py的主循环如何用threading.Timer实现可中断的超时重传# src/server.py 关键片段 def start_server(): sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((localhost, 8080)) while True: data, addr sock.recvfrom(1024) # 解析数据包struct.unpack(!I, data[:4])[0] 取前4字节为SEQ seq_num struct.unpack(!I, data[:4])[0] # GBN服务器收到正确SEQ的ACK才移动window_base if seq_num expected_ack: window_base next_seq_num # 窗口右移 # 取消所有已确认包的重传定时器 for timer in pending_timers: if timer.is_alive(): timer.cancel() pending_timers.clear()这段代码暴露了GBN的核心约束ACK必须按序到达。如果客户端发回ACK3但ACK2丢失window_base不会更新pending_timers里SEQ2的包仍会重传。threading.Timer对象被存入pending_timers列表每次成功ACK后遍历取消——这是避免“幽灵重传”的关键比简单time.sleep()更精准。2.3client.py的双向改造如何用select()同时监听UDP接收与stdin输入停等协议默认单向server→client但课程要求“支持双向”。client.py没用多线程抢socket而是用select.select()做非阻塞轮询# src/client.py 片段 import select sock socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.setblocking(False) # 关键设为非阻塞 while True: # select监听socket接收 sys.stdin用户键盘输入 ready_socks, _, _ select.select([sock, sys.stdin], [], [], 0.1) for ready_sock in ready_socks: if ready_sock sock: # 收到server数据解析SEQ/ACK更新本地状态 data, _ sock.recvfrom(1024) handle_incoming_packet(data) else: # ready_sock sys.stdin # 用户输入新消息封装为client→server的数据包 msg sys.stdin.readline().strip() send_to_server(msg)select.select([sock, sys.stdin], [], [], timeout)让程序在0.1秒内判断是UDP有数据来了还是用户敲了回车两者互不阻塞。这是课程设计里最贴近真实应用的技巧——没有threading.Thread(targetinput_loop)那种线程竞争风险。3. 把config.py调成你的协议调音台超时、窗口、丢包率全参数化3.1config.py里的四大可调参数及其物理意义参数名默认值修改影响实测建议值TIMEOUT_MS2000超时重传阈值。设太小500ms导致频繁误重传太大5000ms降低吞吐局域网用1000~1500模拟公网用3000WINDOW_SIZE4GBN窗口大小。影响并发度窗口1即退化为停等窗口8需更多内存缓存初学建议从2起步观察Wireshark里连续4个SEQ包LOSS_RATE0.0丢包模拟概率。util.py中random.random() LOSS_RATE决定是否drop验证协议健壮性0.110%丢包是黄金测试点PACKET_SIZE1024UDP单包最大载荷。超过65507会IP分片增加丢包率文件传输时设为512避免分片文本聊天可用1024注意LOSS_RATE不是在网络层丢包而是在server.py的sendto()前插入判断——这是课程设计允许的简化但务必在README.md里声明“丢包模拟位于应用层非真实网络丢包”。3.2util.py校验和计算与字节序转换的血泪经验# src/util.py def calculate_checksum(data): RFC 1071校验和16位反码和处理奇数长度 checksum 0 # 按16位2字节累加 for i in range(0, len(data), 2): if i 1 len(data): word (data[i] 8) data[i 1] else: # 奇数长度末尾补0 word data[i] 8 checksum word checksum (checksum 0xffff) (checksum 16) # 进位折叠 return ~checksum 0xffff def pack_packet(seq_num, data): 打包SEQ(4B)CHECKSUM(2B)DATA seq_bytes struct.pack(!I, seq_num) # 大端序4字节 checksum calculate_checksum(seq_bytes data) checksum_bytes struct.pack(!H, checksum) # 大端序2字节 return seq_bytes checksum_bytes data这里有两个玄学坑struct.pack(!I)必须用!network byte order否则客户端用!I解包会错读SEQ校验和计算时data必须包含seq_bytes协议头否则ACK校验失败——很多同学只对DATA算checksum结果永远收不到ACK3.3report.docx里藏着的验证方法论如何用Wireshark抓到你的GBN窗口课程报告要求“验证协议有效性”不能只说“我跑了没报错”。正确做法在server.py启动前Wireshark过滤udp.port8080发送一个10KB文件client_data.txt观察UDP包序列停等模式SEQ0→ 等ACK0→SEQ1→ 等ACK1...GBN模式连续发出SEQ0,1,2,3窗口4然后停住等ACK0才发SEQ4手动在config.py设LOSS_RATE0.3看Wireshark里SEQ2包消失后SEQ2是否在TIMEOUT_MS后重发提示Wireshark里右键UDP包 → “Follow → UDP Stream”能直观看到数据流顺序比数SEQ数字更可靠。4. 避坑那些让client.py卡死、server.py内存爆掉的隐藏雷区4.1 现象客户端运行后无响应ps aux | grep python显示CPU 100%原因client.py中select.select()的timeout参数设为None永久阻塞但sys.stdin在某些IDE如PyCharm里不触发就绪事件导致无限等待。解决强制设为浮点数0.1并在循环内加time.sleep(0.01)防忙等——见2.3节代码。4.2 现象传输大文件时server.py抛MemoryError原因GBN的sent_packets {}缓存所有未确认包PACKET_SIZE1024传10MB文件会缓存约10000个包每个包含datatimestamp内存暴涨。解决在GBN.py的send_packet()里加内存保护if len(sent_packets) 1000: # 限制缓存1000个包 oldest_seq min(sent_packets.keys()) del sent_packets[oldest_seq] # 强制丢弃最老包4.3 现象recv client_recv.txt内容乱码或比server_data.txt少几行原因UDP不保证顺序client.py收到SEQ3包时若SEQ2还没到直接写入文件会导致错序。停等协议没这问题但GBN必须严格按序重组。解决client.py维护received_buffer {}只当SEQ expected_seq时写入文件并向前检查是否有连续SEQ可flushreceived_buffer[seq] data # 检查能否连续写出 while expected_seq in received_buffer: write_to_file(received_buffer[expected_seq]) del received_buffer[expected_seq] expected_seq 14.4 现象修改config.py后重启服务丢包率没变化原因util.py里的LOSS_RATE被server.py和client.py分别import但server.py启动后util.LOSS_RATE已加载后续修改config.py不生效。解决所有模块必须from config import *且config.py里用global LOSS_RATE声明或更稳妥——把LOSS_RATE作为函数参数传入util.drop_packet()。4.5 现象report.docx里截图Wireshark显示[Bad Checksum]原因校验和计算时没包含协议头SEQ字段。calculate_checksum()传入的data只是纯payload但协议规定校验和覆盖SEQDATA。解决见3.2节pack_packet()确保calculate_checksum(seq_bytes data)——这是90%同学翻车的第一步。5. 从停等到GBN再到SR三步改造法把100010493变成你的协议实验沙盒5.1 第一步停等协议→GBN只需动server.py的三处停等协议的server.py核心是# 停等模式发一个等一个ACK send_packet(sock, seq_num, data) wait_for_ack(sock, seq_num) # 阻塞 seq_num 1改成GBN只需替换发送逻辑用for seq in range(window_base, window_base WINDOW_SIZE): send_packet(...)批量发替换等待逻辑删掉wait_for_ack()改为while window_base ! next_seq_num: check_acks()轮询接收添加重传机制threading.Timer(TIMEOUT_MS/1000, resend_unacked, args[window_base])关键细节check_acks()必须解析ACK的SEQ值只当ACK window_base才移动窗口——这是GBN“累积确认”的体现不是收到ACK2就移动而是ACK2表示0,1,2全到了。5.2 第二步GBN→SR重写GBN.py中的ACK处理逻辑选择性重传SR要求客户端为每个包单独ACK非累积服务端维护每个包的独立状态not justwindow_base改造GBN.py# SR模式用字典替代window_base sent_status {} # {seq: sent|acked|timeout} def mark_acked(seq): sent_status[seq] acked # 检查是否所有seq next_seq_num都acked若是则清空缓存 if all(sent_status.get(s, None) acked for s in range(next_seq_num)): sent_packets.clear() sent_status.clear()此时resend_unacked()改为遍历sent_status只重传sent状态的包——这才是SR的精髓不因ACK2丢失而重传SEQ0,1。5.3 第三步用data/目录下的真实文件验证协议鲁棒性别只用client_data.txt1KB文本测试课程设计要求“文件传输应用”必须验证小文件1KBclient_data.txt→ 测试协议基础流程中文件100KB用dd if/dev/urandom oftest.bin bs1024 count100生成 → 测试内存缓存与重传压力大文件1MBwget https://httpbin.org/image/jpeg -O big.jpg→ 测试长时间运行稳定性我一般会这样做先设TIMEOUT_MS5000跑通1MB文件再调低到1000观察client_recv.txtmd5是否等于server_data.txt。一旦md5不一致立刻Wireshark抓包定位是哪个SEQ包被重复发送或丢失——这才是协议调试的正道。从那以后我每次改完GBN.py都强制走一遍“100KB文件LOSS_RATE0.2TIMEOUT_MS1500”的三连测再敢提交代码。Wireshark里看到连续8个SEQ包飞过去然后稳稳收到8个ACK那种手感比print(success)实在一万倍。希望帮到你。本文还有配套的精品资源点击获取
返回列表