本文还有配套的精品资源,点击获取
简介:哈尔滨工业大学计算机网络课程2018年实验二配套Python源码,完整实现选择重传(SR)和后退N帧(GBN)两种可靠数据传输协议。包含独立客户端、服务器及双向通信版本,共7个核心文件:sr.py、sr_client.py、sr_server.py、gbn.py、gbn_client.py、gbn_server.py、gbn_biconnect.py,全部基于标准Socket编程,适配课程实验运行环境。代码结构清晰,关键逻辑处配有中文注释,支持直接运行调试,可用于观察ACK机制、超时重传、窗口滑动、乱序处理等协议行为。附带requirements.txt说明依赖项,目录中还包含基础工程配置文件(.gitignore、.inscode)及参考子模块。注意:不包含任何实验报告文档,原始报告已缺失,仅提供可验证的协议实现逻辑,适合已有报告框架或需深入理解协议细节的学习者参考使用。
1. 项目概述:这不是一份“交作业代码”,而是一套可触摸的协议教具
哈工大计算机网络实验二,对很多计网初学者来说,是第一次真正把课本上那些抽象的“滑动窗口”“累积确认”“超时重传”从二维图示里拽出来、放到真实字节流中去观察和调试的实战关口。我带过三届本科生助教,也自己重写过四轮这套实验——最深的体会是:学生卡住的地方,从来不是不会写for循环,而是不理解为什么窗口大小设为4就刚好能撑住一个RTT,为什么GBN在丢包后要回退到base而不是只重传丢失的那个包,为什么SR的接收方要缓存乱序到达的帧却不能无限缓存。这份源码包,正是为解决这些“为什么”而生的。它不是黑盒脚本,而是用Python+Socket搭建的一套透明化协议沙盒:你改一行窗口尺寸,就能在Wireshark里看到ACK序列立刻变样;你手动kill掉server进程几秒再重启,就能亲眼看见client如何触发超时、如何重发、如何被新server的旧ACK干扰(这就是著名的“重复ACK风暴”问题);你故意在gbn_biconnect.py里注释掉某条send(),就能复现双向连接中ACK与DATA交织导致的序列号错乱。关键词里的“SR协议”“GBN协议”“可靠传输”“Socket编程”“哈工大计网”,每一个都不是标签,而是你调试器里正在跳动的变量名、正在打印的日志行、正在抓包分析的TCP流片段。它面向两类人:一类是已经搭好报告框架、只缺底层逻辑佐证的同学——你可以直接运行sr_client.py连sr_server.py,用nc -u localhost 8080发测试数据,看日志里seq=5的包丢了之后,client是否真的在timeout=2.0s后重发了seq=5~8;另一类是想彻底吃透可靠传输内核的进阶者——你可以打开sr.py,盯着self.recv_window这个字典结构,看它是如何用{seq_num: data}实现乱序缓存的,再对比gbn.py里那个简单的self.base整型变量,体会“选择性重传”与“后退N帧”在内存模型上的根本差异。它不提供答案,但给你一把显微镜,让你亲手拆开协议的齿轮,看清每一颗齿牙是如何咬合的。
2. 协议设计与实现思路深度拆解
2.1 为什么必须区分SR与GBN?核心差异不在代码量,而在状态机本质
很多人初看代码会觉得:“不就是改个重传逻辑吗?GBN重传base到next_seq-1,SR重传所有未确认的,抄一遍就行。” 这种想法会直接导致在gbn_biconnect.py里栽跟头。真正的分水岭,在于接收方状态管理模型。GBN的接收方极其“懒惰”:它只维护一个expected_seq,收到任何小于它的包都直接丢弃(RFC 2018明确要求),收到等于它的才交付上层并递增expected_seq。这种设计让接收方逻辑极简,但代价是发送方必须“保守”——一旦窗口内任意一包丢失,后续所有包都得重传,哪怕它们早已安全抵达。SR则相反,接收方是个“积极分子”:它维护一个recv_window字典,只要收到的seq在[rcv_base, rcv_base + window_size)范围内,就无条件缓存,无论顺序,并立即发送对应ACK。这带来了两个硬性约束:第一,接收方必须能区分“新ACK”和“重复ACK”(所以每个ACK必须携带当前rcv_base);第二,发送方必须能处理“非累积”的ACK(比如收到seq=7的ACK,但seq=5还没到,就不能移动send_base)。翻看sr.py里的handle_ack()函数,你会发现它不是简单地send_base = ack_seq + 1,而是遍历所有已发送但未确认的包,检查ack_seq是否覆盖了某个包的seq,然后逐个标记为已确认——这才是SR“选择性”的灵魂。而gbn.py里对应的逻辑,一行if ack_seq >= self.base: self.base = ack_seq + 1就搞定。这种状态机差异,直接决定了双向连接的复杂度:GBN双向通信时,双方的base和expected_seq必须严格隔离,否则ACK会互相污染;SR则还要额外处理recv_window的跨连接同步问题。所以,gbn_biconnect.py不是gbn_client.py和gbn_server.py的简单拼接,而是重构了整个事件循环,用独立线程分别处理收/发,并为每个方向维护独立的base和expected_seq实例。这解释了为什么目录里有7个文件而非4个——多出的gbn_biconnect.py和sr.py/gbn.py这两个核心协议引擎,才是理解哈工大实验设计意图的关键。
2.2 Socket选型:为什么坚持UDP而非TCP?这不是偷懒,而是教学必需
看到sr_client.py里socket.socket(socket.AF_INET, socket.SOCK_DGRAM)这行,新手常疑惑:“老师讲可靠传输,我们却用不可靠的UDP?这不是自相矛盾吗?” 这恰恰是哈工大实验最精妙的教学设计。TCP本身就是一个巨复杂的GBN/SR混合体(带SACK的TCP更接近SR),如果你直接用TCP写客户端,那所谓的“实现可靠传输”就成了在轮子上再造轮子。UDP提供了干净的“字节管道”:它保证单个报文的完整性(IP层校验和),但不保证送达、不保证顺序、不保证不重复——这正是可靠传输协议需要解决的全部问题域。用UDP,你才能亲手实现:
-超时机制:在sr_client.py的send_packet()里,self.sock.settimeout(self.timeout)设置阻塞超时,try...except socket.timeout:捕获后触发重传;
-序列号与ACK:每个发送的Packet对象都带seq_num字段,接收方解析后构造ACKPacket(seq_num=received_seq)回传;
-窗口管理:self.send_base和self.next_seq_num的更新逻辑,完全由你控制,不受操作系统内核干扰。
如果换成TCP,send()调用成功只代表数据进了内核发送缓冲区,你永远看不到“丢包”;recv()返回的数据已经是按序重组好的,你永远看不到“乱序”。这份代码的价值,就在于它把网络协议栈里被层层封装的“不可靠性”赤裸地暴露给你——你必须直面IP层的丢包、链路层的误码、路由器的拥塞。这也是为什么requirements.txt里只有pycryptodome(用于可选的校验和加密)而没有其他网络库:它刻意保持在POSIX Socket API的最底层,确保你调试时看到的每一个sendto()和recvfrom()调用,都对应着真实网络中的一次物理帧发送或接收。
2.3 双向连接的设计陷阱:为什么gbn_biconnect.py比单向版本难十倍?
单向传输(client发→server收)的逻辑是线性的:client发包→server收包→server回ACK→client收ACK→更新窗口。双向连接(client↔server互发数据)瞬间引入三个维度的并发冲突:
1.序列号空间冲突:client发给server的包用一套seq(0,1,2…),server发给client的包必须用另一套独立seq(0,1,2…),否则ACK会混淆。gbn_biconnect.py里为此定义了ClientSender和ServerSender两个独立类,各自维护self.base和self.next_seq_num;
2.ACK语义歧义:当client收到一个ACK,它必须立刻判断这是对自己发出的DATA包的确认,还是server发来的、针对server自己DATA包的ACK(即“伪ACK”)。解决方案是在每个ACK包里增加direction字段(如'C2S'表示这是client→server方向的ACK),handle_incoming_ack()函数首先解析此字段再路由到对应sender实例;
3.超时定时器竞争:client既要为自己的DATA包启动超时器,又要为server的DATA包启动另一个超时器。gbn_biconnect.py采用单线程事件循环+时间戳队列,每次select()等待前计算所有待超时事件的最小剩余时间,避免多线程锁开销。
这些设计在gbn_biconnect.py的run()方法里体现得淋漓尽致:它用while True:主循环,内嵌select([sock], [], [], timeout)做I/O多路复用,收到数据后先parse_packet()识别类型(DATA/ACK/CONTROL),再根据packet.direction分发给self.client_sender.handle_ack()或self.server_sender.handle_ack()。这种结构看似复杂,但正是工业级协议栈(如QUIC)的简化雏形——它强迫你思考:当网络不再是单向流水线,而是一个多向交织的图谱时,“可靠”二字该如何重新定义。
3. 核心模块解析与实操要点
3.1sr.py与gbn.py:协议引擎的骨架与血肉
这两个文件是整个项目的“心脏”,它们不处理网络I/O,只专注协议逻辑。以sr.py为例,其核心是SelectiveRepeat类,包含四个关键属性:
-self.window_size: 发送窗口大小,直接影响吞吐量与内存占用;
-self.send_base: 当前窗口左边界,即最早未确认的包的seq;
-self.next_seq_num: 下一个待分配的seq,即窗口右边界+1;
-self.sent_packets: 字典{seq_num: (packet, timestamp)},缓存所有已发未确认的包及发送时间,用于超时检测。
send_packet()方法的逻辑链条极为清晰:
1. 检查self.next_seq_num - self.send_base < self.window_size,确保不越界;
2. 构造Packet(seq_num=self.next_seq_num, data=data, checksum=calc_checksum(data));
3. 将包存入self.sent_packets[self.next_seq_num] = (packet, time.time());
4. 调用外部send_func(packet)(由client/server注入)实际发送;
5.self.next_seq_num += 1。
最关键的handle_ack()方法,则展示了SR的精髓:
def handle_ack(self, ack_seq): # SR的ACK是“选择性”的,可能确认任意一个已发包 if ack_seq in self.sent_packets: del self.sent_packets[ack_seq] # 移除已确认包 # 检查是否能向前滑动send_base while self.send_base in self.sent_packets: self.send_base += 1 # 注意!这里不是ack_seq+1,而是检查连续确认这段代码揭示了一个反直觉事实:SR的send_base不是由ACK直接驱动的,而是由sent_packets字典的连续性决定的。只有当send_base对应的包被确认,且send_base+1也在字典中(意味着它已发但未确认),send_base才不会移动;一旦send_base被确认且send_base+1不在字典中(说明它还没发或已超时重发),send_base就会跳到下一个存在的key。这与GBN中self.base = max(self.base, ack_seq + 1)的累积式更新形成鲜明对比。实操时,你可以在sr_client.py的main()函数里加一行print(f"ACK received: {ack_seq}, send_base now: {sr.send_base}"),然后故意断开server,观察send_base如何在超时重传后停滞不前——这就是SR“选择性”的代价:它不强制要求ACK连续,因此发送方无法仅凭单个ACK就推进窗口。
3.2sr_client.py与sr_server.py:Socket层的胶水与调试接口
如果说sr.py是大脑,那么sr_client.py就是手和脚。它的核心在于将协议引擎与网络世界连接起来。sr_client.py的main()函数流程如下:
1. 创建UDP socket并绑定端口;
2. 初始化SelectiveRepeat实例,传入window_size=4,timeout=2.0等参数;
3. 启动一个独立线程start_receiver_thread(),持续recvfrom(1024)监听ACK;
4. 主线程循环读取用户输入(或文件),调用sr.send_packet(data)发送;
5. 在start_receiver_thread()里,每收到一个ACK,就调用sr.handle_ack(ack_seq)更新状态。
这里有个极易忽略的调试技巧:在start_receiver_thread()的recvfrom()后,立即打印原始字节流。例如:
data, addr = sock.recvfrom(1024) print(f"[DEBUG] Raw ACK bytes: {data.hex()}") # 输出类似 '0000000500000000'这样你能看到ACK包的实际结构:前4字节是seq_num(小端序),后4字节是checksum。当你发现client收不到ACK时,第一反应不该是查逻辑,而是用这条print确认:server是否真的发出了ACK?发出的ACK的seq_num是否正确?checksum是否为0(表示server没计算校验和)?这种底层字节级的验证,是绕过所有高级抽象直击问题根源的最快路径。同理,sr_server.py的handle_data_packet()里,在deliver_to_app_layer(packet.data)前加print(f"Server received seq={packet.seq_num}, len={len(packet.data)}"),能让你实时看到乱序包的到来顺序,验证recv_window是否真的在缓存它们。
3.3gbn_biconnect.py:双向通信的完整工作流实录
这是全项目最值得逐行精读的文件。它实现了client与server之间真正的“对话”。其run()方法是事件驱动的典范:
def run(self): while self.running: # 计算所有待处理事件的最小超时时间 timeout = self.get_min_timeout() # 使用select等待I/O或超时 ready, _, _ = select.select([self.sock], [], [], timeout) if ready: # 有数据到达 data, addr = self.sock.recvfrom(1024) packet = parse_packet(data) if packet.type == 'DATA': self.handle_data_packet(packet, addr) elif packet.type == 'ACK': self.handle_ack_packet(packet) else: # 超时事件发生 self.handle_timeout_events()handle_data_packet()的处理逻辑揭示了双向连接的核心:
- 若packet.direction == 'C2S'(client to server),则调用self.server_sender.handle_ack(packet.ack_seq)更新server的发送窗口;
- 若packet.direction == 'S2C'(server to client),则调用self.client_sender.handle_ack(packet.ack_seq)更新client的发送窗口;
- 同时,它会为该DATA包生成一个ACK,并设置ack.direction = packet.direction(即C2S的DATA回C2S的ACK),确保ACK语义闭环。
实操中,你可以用netcat模拟异常:在client运行时,执行echo "corrupt" | nc -u 127.0.0.1 8081向server端口发垃圾数据,观察gbn_biconnect.py的parse_packet()如何因checksum失败而丢弃该包,并触发self.server_sender.resend_from_base()——这就是协议鲁棒性的现场演示。注意,此时client的发送窗口不受影响,因为它只关心自己发出的DATA包的ACK,这种隔离性正是双向连接稳定的基础。
4. 实操过程与核心环节实现
4.1 环境准备与一键运行指南
无需复杂配置,这套代码专为哈工大实验环境优化。实操步骤极度精简:
1.克隆仓库并进入目录:bash git clone https://github.com/xxx/HIT-CNet-Lab2.git cd HIT-CNet-Lab2
2.安装依赖(仅需标准库):requirements.txt里只有一行pycryptodome>=3.9.9,用于可选的校验和计算。若只需基础功能,甚至可以删除该依赖,将calc_checksum()函数改为return 0(此时校验和失效,但协议逻辑仍可运行)。推荐安装:bash pip install pycryptodome
3.启动服务端(任选一种):
- GBN单向:python gbn_server.py --port 8080
- SR单向:python sr_server.py --port 8081
- GBN双向:python gbn_biconnect.py --client_port 8080 --server_port 8081
4.启动客户端(对应服务端):
- 连GBN服务端:python gbn_client.py --host 127.0.0.1 --port 8080
- 连SR服务端:python sr_client.py --host 127.0.0.1 --port 8081
- 连GBN双向:python gbn_client.py --host 127.0.0.1 --port 8080(此时client既发又收)
提示:所有脚本均支持
--help查看参数。--window_size 4可动态调整窗口,--timeout 1.5可修改超时时间。建议首次运行用默认值,熟悉后再调整。
4.2 关键行为观测实验:用三分钟验证一个核心概念
不要急于跑通整个流程,先做三个微型实验,亲手验证协议本质:
实验一:验证GBN的“后退”特性
1. 启动gbn_server.py --port 8080;
2. 启动gbn_client.py --port 8080,输入hello world;
3. 在server终端,当看到Received packet seq=0后,立即按Ctrl+C终止server;
4. 等待约2秒(默认timeout=2.0),client会打印Timeout! Resending from base=0,并重发seq=0~3;
5. 此时重启server:python gbn_server.py --port 8080;
6. 观察server日志:它会收到seq=0(交付),seq=1(丢弃,因为expected_seq=1),seq=2(丢弃),seq=3(丢弃)——这就是GBN的“后退”:client重发了整个窗口,但server只收第一个,其余全丢。
实验二:验证SR的“选择性”与“缓存”
1. 启动sr_server.py --port 8081;
2. 启动sr_client.py --port 8081,输入A(seq=0)、B(seq=1)、C(seq=2)、D(seq=3);
3. 在server收到A(seq=0)后,手动删除server进程;
4. client会超时重传seq=0,但此时你快速重启server;
5. server先收到seq=0(交付),再收到seq=2(缓存,因为rcv_base=1),再收到seq=1(交付并触发交付seq=2),最后收到seq=3(缓存)——recv_window字典里始终只有{2: b'C', 3: b'D'},直到seq=1到达才清空。
实验三:验证双向连接的ACK隔离
1. 启动gbn_biconnect.py --client_port 8080 --server_port 8081;
2. 启动两个client:python gbn_client.py --port 8080(作为client A),python gbn_client.py --port 8081(作为client B);
3. A发Hello,B发World;
4. 观察A的log:它只处理来自端口8081的ACK(即server对A的DATA的确认),完全忽略B发来的、目标端口8080的ACK(那是server对B的DATA的确认)——direction字段在此刻成为防火墙。
4.3 日志与调试技巧:让协议“开口说话”
代码里预埋了大量调试钩子,善用它们事半功倍:
-全局日志开关:所有.py文件顶部都有DEBUG = True常量,设为False可关闭所有print();
-分层日志:sr_client.py里有CLIENT_LOG、SERVER_LOG、PROTOCOL_LOG三级标识,可在print()前加if DEBUG and CLIENT_LOG:精确控制输出;
-Wireshark联动:启动wireshark -f "udp port 8080 or udp port 8081",过滤出你的流量,对照代码里的Packet结构(seq_num占4字节,data紧随其后),用Tools → Protocol Preferences → UDP设置解码规则,让Wireshark直接显示seq_num字段;
-时间戳锚点:在send_packet()和handle_ack()里加入time.time()打印,计算端到端延迟:recv_time - send_time,你会看到GBN的延迟波动远大于SR——因为GBN的“后退”导致有效吞吐率下降。
注意:
gbn_biconnect.py的handle_timeout_events()里有一段被注释的print(f"Resending from base={self.client_sender.base}..."),取消注释后,你能清晰看到双向连接中,client和server各自的重传事件如何交错发生,这是理解并发协议行为的黄金视角。
5. 常见问题与排查技巧实录
5.1 典型问题速查表
| 问题现象 | 可能原因 | 排查命令/操作 | 解决方案 |
|---|---|---|---|
client一直打印Timeout! Resending...,server完全无日志 | client未正确发送,或server端口错误 | netstat -an \| grep :8080确认server是否监听;tcpdump -i lo udp port 8080确认是否有包发出 | 检查client的--host是否为127.0.0.1(非localhost,避免IPv6解析);确认server的--port与client的--port一致 |
server收到包但不打印Received packet | checksum校验失败 | 在server.py的handle_data_packet()里,print(f"Raw data: {data}")后加print(f"Calculated checksum: {calc_checksum(data[8:])}") | 检查calc_checksum()是否被正确调用;若用pycryptodome,确认data[8:]切片是否准确(前8字节为seq+checksum) |
| 双向连接中,client A发的数据,client B收到了ACK | direction字段未正确设置或解析 | 在gbn_biconnect.py的handle_ack_packet()里,print(f"ACK direction: {packet.direction}, expected: {expected_dir}") | 确认packet.direction在parse_packet()中是否从固定偏移读取;检查client A和B是否使用了不同端口,避免ACK路由错误 |
修改window_size=8后,client崩溃或server丢包严重 | 窗口过大导致UDP分片或buffer溢出 | ip link show查看MTU;ss -m \| grep udp查看socket buffer大小 | 将window_size限制在min(8, (MTU-28)//(data_size+8)),其中28为IP+UDP头,8为seq+checksum |
5.2 我踩过的五个坑与独家修复技巧
坑一:UDP的“原子性”幻觉
新手常以为sendto()发100字节,recvfrom()一定收100字节。实际上,UDP报文最大65507字节,但链路MTU通常为1500,超过则IP层分片。若任一分片丢失,整个UDP报文就被丢弃。我在sr_client.py里曾用window_size=16发大数据块,结果大量超时——tcpdump显示server只收到部分分片。修复技巧:在send_packet()前强制限制len(data) <= 1400,并在注释里写明“避免IP分片,确保单个UDP报文可达”。
坑二:时间精度陷阱time.time()返回浮点秒,在毫秒级超时场景下误差显著。gbn_client.py里timeout=0.5时,time.time() - start_time > timeout常因浮点误差提前触发。修复技巧:改用time.perf_counter(),它提供单调递增的高精度计时器,start = time.perf_counter(); ... if time.perf_counter() - start > timeout:。
坑三:字节序的隐形杀手struct.pack('!I', seq_num)用网络字节序(大端),但若server用struct.unpack('I', data[:4])(默认主机字节序),在x86机器上就会错读seq。我在gbn_biconnect.py调试时,client发seq=1,server解析成16777216。修复技巧:统一用!I(网络字节序),struct.unpack('!I', data[:4])[0],并在Packet类文档字符串里强调“所有整数字段均为网络字节序”。
坑四:多线程资源竞争sr_client.py的receiver线程与主线程共享self.sr实例,若主线程调用send_packet()同时receiver调用handle_ack(),可能破坏sent_packets字典。修复技巧:在SelectiveRepeat类中添加self._lock = threading.Lock(),所有修改sent_packets或send_base的操作前加with self._lock:。
坑五:双向连接的“心跳”缺失gbn_biconnect.py在长时间无数据时,双方窗口会停滞。若client发完数据就静默,server的base永远卡住。修复技巧:在run()循环末尾添加心跳逻辑:if time.time() - last_activity > 30: self.send_heartbeat(),发送一个type='HEARTBEAT'的空包,维持连接活性。
6. 协议扩展与工程化思考
6.1 从实验代码到工业级协议的鸿沟与桥梁
这套代码是完美的教学载体,但它离生产环境还有三道坎:
-拥塞控制缺失:实验假设带宽无限,而真实网络需实现慢启动、拥塞避免(如TCP的cwnd机制)。可在handle_ack()中加入self.cwnd = min(self.cwnd + 1, self.ssthresh),当连续收到3个重复ACK时触发快重传;
-安全性空白:UDP无加密,数据明文传输。requirements.txt中的pycryptodome已预留接口,可扩展encrypt_payload()和decrypt_payload(),用AES-GCM实现认证加密;
-连接管理粗放:当前无握手/挥手,gbn_biconnect.py靠进程启停模拟连接。真正的双向协议需三次握手(SYN, SYN-ACK, ACK)建立连接,四次挥手(FIN, ACK, FIN, ACK)释放资源。
这些扩展并非画蛇添足。我在某物联网项目中,正是基于类似的SR原型,增加了LWM2M协议的CoAP over UDP封装,用gbn_biconnect.py的双向框架支撑设备与云平台的指令下发与状态上报。关键在于:教学代码的价值,不在于它多完美,而在于它多“可生长”——它的模块化设计(协议引擎与I/O分离)、清晰的状态变量(send_base, next_seq_num)、标准化的Packet结构,为所有扩展提供了稳固的基座。
6.2 给哈工大学弟学妹的终极建议
别把这份代码当答案抄,把它当X光片看。我的建议是:
1.先删后写:拿sr_client.py,删掉所有sr.send_packet()调用,自己重写一个极简版my_send(),只处理seq_num分配和sendto,再逐步加入超时、ACK处理;
2.逆向工程日志:关掉所有DEBUG,只留server的print("Delivered:", data),然后用nc -u 127.0.0.1 8080发hello,观察输出。若没输出,证明client根本没发出去——这时再开DEBUG,一层层往上查;
3.用Wireshark代替脑补:与其猜“ACK是不是发错了”,不如直接看Wireshark里udp.dstport==8080 && udp.length>12的包,数seq_num是否连续,checksum是否为0;
4.接受“不完美”:实验报告里写“在window_size=6时,吞吐量达到峰值,但丢包率上升至12%”,这比编造一个“100%可靠”的假数据更有价值——真实网络本就如此。
最后分享一个小技巧:在sr.py的__init__()里,把self.window_size设为一个property,添加setter方法:
@property def window_size(self): return self._window_size @window_size.setter def window_size(self, value): print(f"[INFO] Window size changed to {value}") self._window_size = value这样,当你在client里动态调整窗口时,控制台会实时告诉你协议状态正在变化——这小小的print,就是你与协议对话的第一句问候。
本文还有配套的精品资源,点击获取
简介:哈尔滨工业大学计算机网络课程2018年实验二配套Python源码,完整实现选择重传(SR)和后退N帧(GBN)两种可靠数据传输协议。包含独立客户端、服务器及双向通信版本,共7个核心文件:sr.py、sr_client.py、sr_server.py、gbn.py、gbn_client.py、gbn_server.py、gbn_biconnect.py,全部基于标准Socket编程,适配课程实验运行环境。代码结构清晰,关键逻辑处配有中文注释,支持直接运行调试,可用于观察ACK机制、超时重传、窗口滑动、乱序处理等协议行为。附带requirements.txt说明依赖项,目录中还包含基础工程配置文件(.gitignore、.inscode)及参考子模块。注意:不包含任何实验报告文档,原始报告已缺失,仅提供可验证的协议实现逻辑,适合已有报告框架或需深入理解协议细节的学习者参考使用。
本文还有配套的精品资源,点击获取