简介:TCP-RDT2.2.zip 是一份面向计算机网络课程的可靠数据传输协议实验源码,适用于高校网络专业学生与 TCP 原理学习者。资源以 Java 实现 RDT 2.2,即在 RDT 2.0 基础上升级,通过累计确认、超时重传与纠错编码解决 ACK 包位错问题,帮助读者从代码层面理解序列号、校验和、RTT 超时等可靠传输核心机制。压缩包共 15 个文件,以 4 个 Java 源码及对应 class 编译文件为主体,辅以 txt 数据日志与 Eclipse 工程配置(.project、.classpath、Config.ini 等),容量仅 1.04MB,解压即可导入 IDE 运行调试。目前已有 323 人浏览学习,对于正在完成 TCP_RDT2.2 实验或复习 TCP 原理的学习者,这份工程既能作为可直接运行的模板,又能通过源码对照理解可靠传输协议的设计与实现细节,是课程实践和期末备考的实用参考资料。
1. TCP-RDT2.2.zip:先搞清楚这是个协议模拟包,不是真正的 TCP 连接
拿到 TCP-RDT2.2.zip 的同学,多半是在计算机网络课设或者面试复习里碰到了“可靠数据传输”这一节。这个压缩包里的主角是 RDT2.2,一个常被误当成“TCP 源码”的协议实现,实际上它只是传输层可靠机制的最小教学模型。它解决的是“底层信道可能丢包、出错时,上层怎么保证收到的数据是对的”这个问题,适合想搞懂 TCP 连接为什么需要序号、校验和、重传的人。这个包能让你在本地跑起来、抓包验证,然后理解 TCP 协议栈里那套可靠传输逻辑到底是怎么从理论落地的。
2. 拆包之前先拆协议:RDT2.2 在 TCP 协议栈里到底管哪一层
2.1 RDT 演进:从假设信道永远不坏,到容忍位错误
RDT 是 Reliable Data Transfer 的缩写。计算机网络教材讲到传输层时,会用一组逐步复杂的模型来解释 TCP 的可靠传输是怎么来的。RDT 1.0 假设信道完全可靠,收发双方只管接收和发送,不检查、不确认;RDT 2.0 引入校验和(checksum)和 ACK/NAK,用来发现并反馈接收端收到的数据有没有发生位翻转;RDT 2.1 把序号(sequence number)加上,解决 ACK/NAK 本身丢失造成接收端重复交付数据的问题;再到 RDT 2.2,做了一次关键修正:把 NAK 去掉,只靠 ACK 和序号来工作,不做否定确认,而是通过“重复 ACK”来触发重传。
到 RDT 3.0 才是真正完整版本,引入超时定时器。很多教材把 RDT 2.2 看作是“没有定时器但逻辑自洽”的版本,因为这个版本里默认分组不会丢,只会损坏或乱序。这也是为什么学完 RDT 2.2 之后再看 RDT 3.0 会觉得顺理成章。这个演化顺序本质上就是在模拟 TCP 协议栈在传输层解决“不可靠信道上的可靠传输”的全部手段。
| 版本 | 处理的问题 | 核心机制 |
|---|---|---|
| RDT 1.0 | 无 | 直接发,直接收,假设信道不出错 |
| RDT 2.0 | 位错误 | 校验和 + ACK/NAK |
| RDT 2.1 | 分组重复 / ACK 出错 | 分组加序号 |
| RDT 2.2 | NAK 失效场景 | 去掉 NAK,用重复 ACK 通知重传 |
| RDT 3.0 | 丢包 | 超时重传 |
2.2 RDT2.2 与 TCP 连接的分工
TCP 连接是面向字节流的可靠连接,它的可靠传输建立在四个机制上:校验和、序号、确认、重传。RDT2.2 把前三个机制做了最小实现,但缺了“连接管理”和“流量/拥塞控制”。三次握手、四次挥手、窗口调整这些,在 RDT2.2 里完全不存在。所以不要指望这个包能代替真正基于 TCP 协议栈的 socket 编程。它的价值在于把“可靠传输”这一块单独抽出来,让你能看到一个数据包从发送端到接收端,中间经历了哪些状态变化。
一个常见的误解是“RDT2.2 就是 UDP”。从实现角度,很多人都乐意用 UDP socket 做底座来跑 RDT2.2,因为 UDP 提供的是尽力而为的服务,不保证有序、不保证不丢,正好充当那个“不可靠信道”。协议栈里实际承担可靠传输的 TCP 并不直接暴露给你看,而 RDT2.2 相当于在应用层自己搭了一套可靠传输逻辑。这个模型的本质是:把 UDP 当 IP,把 RDT2.2 当 TCP 的传输逻辑。
2.3 为什么要用 UDP 当底座:一个能动态破坏的测试信道
如果直接拿一个真正的 TCP 连接来验证可靠传输,你很难观测中间发生了什么,因为 TCP 协议栈把重传、排序都处理掉了,应用层看到的永远是有序的数据。而 UDP socket 让你能控制每一次 sendto 是否真的发出、是否在接收端丢弃,甚至可以在收发之间加一个“坏信道层”。我一般是把 UDP 报文里面塞 RDT2.2 头,人为地做四件事:改一个字节破坏校验和、随机丢包、复制数据、调换顺序。用 TCP 连接做这些实验就得改内核参数,太麻烦。
下面给一个用 Python 搭这个测试信道最小骨架的常见做法。它不完整,但能帮你理解 RDT2.2 数据包的格式与校验和是怎么在 UDP 载荷里组织起来的。
import socket import struct def rdt_checksum(data: bytes) -> int: # 对齐到16位,按TCP风格做二进制反码求和 if len(data) % 2 == 1: data += b'\x00' s = 0 for i in range(0, len(data), 2): word = (data[i] << 8) + data[i+1] s += word s = (s & 0xffff) + (s >> 16) # 进位回卷 return (~s) & 0xffff def build_rdt_packet(seq: int, payload: bytes) -> bytes: checksum = rdt_checksum(struct.pack('!H', seq) + payload) # 头部:2字节序号 + 2字节校验和 return struct.pack('!HH', seq, checksum) + payload # 一个简单得不能再简单的UDP接收端 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('127.0.0.1', 9999)) while True: pkt, addr = sock.recvfrom(2048) seq, checksum = struct.unpack('!HH', pkt[:4]) if rdt_checksum(pkt[4:]) == checksum: print(f'seq={seq} checksum ok payload={pkt[4:].decode()}') else: print(f'seq={seq} checksum FAIL, drop it')这段代码的逻辑说明:发送端把序号加校验和放在头部,接收端先校验头部的 checksum 与去掉头后数据算出来的 checksum 是否一致,不一致就丢弃。注意 rdt_checksum 函数里回卷那行是关键,如果不加(s & 0xffff) + (s >> 16),高位进位会丢失,校验和算出来看着对,实际会漏掉某些翻转。这个坑在 5.2 节还会再讲。
参数说明:选择!HH是为了让头部以网络字节序排列,序号字段 2 字节,校验和字段 2 字节,这在教学版本里是标配。端口号 9999 随便挑一个没被占用的 UDP 端口就行。如果你想做吞吐测试,recvfrom 的缓冲区要加大,否则高速跑的时候 UDP 层会直接丢包,你会误以为是信道问题。
3. 在本地跑通 TCP-RDT2.2.zip:解压、编译、第一次收发
3.1 解压后先别急着跑:看目录结构决定用什么工具链
解压 TCP-RDT2.2.zip 之后,通常你看到的是这么几类文件:协议头定义(比如rdt.h)、发送端实现、接收端实现、一个模拟信道或者测试脚本、还有 README 或课程报告模板。常见的实现语言有两种,一种是 C,用 POSIX socket 直接操作 UDP;另一种是 Python,结构清晰,便于改参数。如果你打开 README 看到makefile或者.c结尾的文件,那就是 C 版本,需要 gcc;如果只有.py,那就直接用 Python 3 跑。
解压之前有一个高频问题:zip 包损坏。有人就遇到过invalid zip archive: could not find eocd,这几乎都是下载中断造成的。处理办法是重新下载后用校验值核对。如果你在 Linux 下解压,我习惯先跑unzip -t TCP-RDT2.2.zip做完整性测试,它会逐个文件检查 CRC。
unzip -t TCP-RDT2.2.zip # 输出的最后一行如果是 OK / no errors,才继续解压 unzip TCP-RDT2.2.zip -d rdt2.2/ cd rdt2.2 ls -la这段命令的逻辑说明:unzip -t是测试模式,它只读不写,把 zip 里每个文件的压缩流解出来算 CRC 和头部记录的 CRC 对比。如果某个文件的 CRC 对不上,或者 zip 格式本身有问题,这里就会报警。确认没问题后再真正解压,避免解到一半报错留下半套文件。解压到子目录而不是直接铺在当前目录,是为了后边清理方便。
3.2 编译与运行的最小命令:C 版本和 Python 版本各跑一遍
如果你拿到的是 C 版本,编译命令一般是这样的。注意这里别自作主张加-O2,教学代码里常常有为了调试方便打印状态机的输出,开了优化可能会把某些 print 优化掉预期行为,反而看不懂接下来的状态切换。
# C 版本常见结构:sender.c receiver.c rdt.c rdt.h gcc -Wall -g -o sender sender.c rdt.c gcc -Wall -g -o receiver receiver.c rdt.c ./receiver 127.0.0.1 8888 & sleep 1 ./sender 127.0.0.1 8888 data.txt逻辑说明:先起接收端再起发送端,是因为发送端一旦开始送数据,接收端没准备好就会把第一批数据丢进 UDP 黑洞。-g生成调试信息,万一崩了可以用 gdb 定位。端口 8888 只是约定值,双方一致即可。有些教学版本里 sender 的参数是“接收端地址 + 端口 + 要发送的文件”,也有把窗口大小、超时时间做成命令行参数的版本,具体看 README。
Python 版本就简单得多,一般是一个文件同时带--receiver和--sender两个运行模式,或者两个脚本分别运行。用python3 receiver.py和python3 sender.py就能跑。这里提醒一个重要参数:Python 的socket.recvfrom默认是阻塞的,命令行跑接收器时,按 Ctrl+C 退出后端口不会立刻释放,重新启动同一个端口会报address already in use。这不是代码问题,也不影响 UDP 单播实验,但如果你做循环测试脚本,就会很烦。
3.3 跑通后怎么验证它真的在做可靠传输
光看到屏幕上打印“发送 seq=0”和“接收 seq=0”不算验证。可靠的验证手段是抓包。在本地跑的时候,因为源目地址都是 127.0.0.1,tcpdump 抓 loopback 接口就行。
sudo tcpdump -i lo -XX 'udp port 8888'抓包输出里你会看到 UDP 报文里带着一串自定义头部字节。和直接发 UDP 的区别在于,RDT2.2 的载荷开头有固定的两个字段可读。如果抓包里的十六进制数据跟你在代码里组装的seq和checksum对得上,网络序也正确,那链路就是通的。如果你想看得更清楚,可以在接收端故意改一个字节,然后重跑发送端,观察抓包里会出现重复的包,那就是重传路径生效了。
更细的验证是在模拟信道层做参数化破坏实验,比如设置丢包率 5%、损坏率 1%,跑一个 1MB 的文件传输,最后用md5sum对比收发双方文件是否一致。协议栈如果实现正确,输出文件应该与输入文件逐字节相同。对比成功本身就能说明校验、重传和去重逻辑没有明显毛病。
3.4 需要关注的配置文件与参数表
TCP-RDT2.2 这类教学包通常把参数集中在头文件或脚本开头,常见的有下面几个。搞清楚它们的单位,能省下大量查日志的时间。
| 参数 | 常见取值 | 说明 |
|---|---|---|
| SEQ_BITS | 1 或 16 | 1 是课本最小实现,16 能支持更大窗口 |
| TIMEOUT_MS | 500 | 超过这个时间没收到 ACK 就重传 |
| MAX_PKT_SIZE | 1024 字节 | UDP 载荷上限,别超过 1472(以太网 MTU 减去 IP/UDP 头) |
| DROP_RATE | 0~1 | 模拟信道丢包概率,0.05 适合测试 |
| CORRUPT_RATE | 0~1 | 破坏校验和的概率,0.01 已经足够触发错误路径 |
| WINDOW_SIZE | 1 | RDT2.2 版本里值只能是 1,改大就是 RDT3.0 的方向 |
这段参数说明很重要:RDT2.2 停等协议决定了 WINDOW_SIZE 是 1,一次只允许一个分组在途。如果你把这个值改成大于 1,那就不再是 RDT2.2,需要同时改接收端逻辑,支持缓存乱序分组,这就是第 6 章要讲的扩展方向。
4. RDT2.2 状态机与参数设计:从看懂代码到改得动代码
4.1 发送方和接收方各自的状态机
RDT2.2 的发送方只有两个状态:等待上层调用(等待发送许可),以及等待 ACK。接收方也只有两个状态:等待 0 号包,等待 1 号包。因为序号字段是 1 位,在停等协议里正好够用,两个分组互斥地在途,不会出现序号混乱。
发送方的关键转移是这样:发送seq=0的数据包后,进入“等待 ACK0”状态;收到ACK0,确认校验和正确且序号匹配,回到“等待调用”状态,把序号翻转为 1;收到ACK1或者校验失败,则不发新数据,而是重新发送seq=0的旧包。这里最容易写错的是不区分“重复 ACK”和“新 ACK”:RDT2.2 里重复 ACK 是合法的重传触发信号,不是错误状态。
接收方的转移:在“等待 0”状态收到seq=0且校验通过,向上层交付数据,回复ACK0,进入“等待 1”状态;如果收到的又是seq=0(说明上一个 ACK 丢了,发送方重传),接收方不能直接丢弃,而是要重新回复ACK0,并且不能向应用层重复交付数据。这个“重发 ACK 但不上交数据”的动作才是 RDT2.2 区别于 RDT2.1 的巧妙之处:它用重复 ACK 代替了 NAK,让发送方知道“上次的包我收到了,你发下一个”。
| 状态 | 收到包 | 动作 | 下一状态 |
|---|---|---|---|
| 等待 0 | seq=0,校验通过 | 交付数据,发 ACK0 | 等待 1 |
| 等待 0 | seq=0,校验失败 | 重发 ACK1 | 等待 0 |
| 等待 0 | seq=1,校验通过 | 丢弃,重发 ACK0 | 等待 0 |
| 等待 1 | seq=1,校验通过 | 交付数据,发 ACK1 | 等待 0 |
| 等待 1 | seq=1,校验失败 | 重发 ACK0 | 等待 1 |
| 等待 1 | seq=0,校验通过 | 丢弃,重发 ACK1 | 等待 1 |
这个表值得一步步对照代码看。你会发现发送方永远不会主动发“否定”信息,接收方也永远不会告诉对方“你发错了”。校验失败时接收方回的是一个旧 ACK,发送方收到旧 ACK 后知道对方要的是当前这个包,于是重传。这套逻辑在丢包场景下也一样成立,只是丢包时发送方收不到任何 ACK,需要靠超时来兜底。
4.2 校验和字段该多长:别图省事用 8 位
课本上的 RDT2.2 一般说校验和是 16 位,和 TCP 头部里的 checksum 一致。有人偷懒用 8 位,在测试环境里确实能跑,但错误检测率会明显下降。16 位校验和的碰撞概率对教学实验足够,8 位的话,你输入的测试数据很容易凑出一个刚好抵消的错误组合,导致“损坏了但校验通过了”的假阴性。
TCP 的 checksum 是 16 位二进制反码和,计算时还会带一个伪首部(pseudo header),把源 IP、目的 IP、协议号、TCP 长度都卷进去。RDT2.2 里没有 IP 层信息,所以伪首部这步直接省略。这也说明 RDT2.2 的校验和保护范围只覆盖到自己的头部和数据,保护不了 IP 地址在传输途中被篡改的情况。这是教学模型的边界,真做产品要用 TCP 或加 DTLS。
实现校验和的时候有个高频错误:求和时按小端序把数据拆成字,最后算出来的值跟按大端序拆出来的结果不同。抓包工具显示的是网络序,如果代码里用主机序算校验和,在 x86 机器上收发两端都用同样的错误方式算,反而能互相校验通过,但你把同样的包丢给抓包器或者另一台大端架构机器,就会校验失败。统一按网络序拆字,避免这层混乱。
4.3 超时重传参数:固定的 500ms 能让实验跑通,但离 TCP 差得远
RDT2.2 如果按严格定义不处理丢包,超时定时器是不需要的;但做实验时几乎都会把信道丢包率调成非零,所以实现里总得有超时。教学代码常写死一个TIMEOUT_MS=500,这个值在 127.0.0.1 环境下绰绰有余——本地回环延迟通常不到 1ms,500ms 等于给足了余量。
但当你把这个模型放到真实网络环境里测,比如两台隔着公网的机器,固定超时就有问题了。公网 RTT 波动很大,500ms 可能在高峰期不够,凌晨又显得浪费。真正的 TCP 是用指数加权移动平均(EWMA)估算 RTT,再乘一个系数得到 RTO(重传超时时间)。常见的简化做法是:
srtt = 0.875 * srtt + 0.125 * rtt # 平滑 RTT rto = max(200, srtt * 2) # 超时取两倍平滑 RTT,保底 200ms把这个式子嵌进你的 RDT2.2 实现里,替换掉固定TIMEOUT_MS,你会发现同样的丢包率下,文件传完的时间明显变短。这就是从课本代码走向真实 TCP 参数化的第一步。注意保底下限 200ms 是必须的,因为recvfrom的时钟精度和调度延迟会引入噪声,设太短会导致一堆无意义重传。
4.4 单工改全双工:两个方向各跑一套状态机
RDT2.2 的课程实现几乎都是单工的:一个进程只发,另一个只收。实际 TCP 连接是双向的,每个方向都有自己的序号空间、自己的确认链。把 RDT2.2 改成全双工的常见做法,是在同一个 UDP socket 上同时跑两套独立的状态机:一个管发送、一个管接收,各自维护各自的expect_seq,收到对方的数据包时,在回执里带上对反方向数据的确认。
一个容易踩的设计坑:发送方和接收方的 ACK 报文会互相干扰。如果你把 ACK 也当成一种独立数据帧,那两边的状态机就得为“帧类型”这个概念加一层判断。更稳妥的做法是把确认号塞进数据帧的头部字段里,数据帧和 ACK 帧合并,即所谓的 piggyback(捎带确认)。TCP 就是这么做的,所以 TCP 报文里既有 seq 也有 ack。RDT2.2 教学版通常没有这个设计,改成全双工时如果忘了捎带确认,你会看到链路利用率直接砍半,因为每个数据包都要等对面单独回一个 ACK 包。
5. 常见问题与避坑:重传风暴、校验失效、UDP 黑洞
5.1 丢包率才 5%,重传却几倍于正常,甚至卡死
现象:模拟信道丢包率调成 0.05,按理说重传比例也应该在 5% 左右,实际却看到发送端日志里重传数量是正常发送的好几倍,整体吞吐掉到几乎不可用。
原因:超时时间设置和 ACK 延迟不匹配。很多人把超时设成 100ms,本地跑没问题,一旦接收端的处理循环里加上打印调试信息,接收变慢,ACK 延迟超过超时阈值,发送端开始重传;重传的包和迟到的 ACK 又在信道上相遇,接收端收到两个一样的包,回两个 ACK,发送端误以为是新 ACK(因为序号可能刚好翻转),于是收发双方进入互相追赶的循环。
解决:先调大超时到 2~5 倍当前 RTT,同时把调试打印移到文件而不是 stdout。stdout 在终端渲染时非常慢,几百个包就能把线程拖垮。本地实验里看到莫名其妙的重传风暴,第一件事先查调试输出是不是终点。
5.2 校验和明明算出来了,某些字节被篡改却检测不到
现象:在模拟信道层随机翻转数据字节,跑完实验后对比文件,发现偶尔会出现“校验通过但内容不对”的结果,概率不高但一旦出现就破坏数据完整性。
原因:求和时没有把进位回卷,或者用 32 位整数直接累计,最后截断到 16 位,中间的进位丢失。TCP 校验和的规范是二进制反码和,每加一个 16 位字,如果产生进位,要把进位加回到最低位。用 Python 的struct.pack('!H', ...)时同样要处理回卷,不能直接sum & 0xffff。
解决:照 2.3 节那个 rdt_checksum 实现,循环里每次累加后立刻执行s = (s & 0xffff) + (s >> 16),比最后统一回卷更稳。另外一个隐蔽点:做校验时要把 seq 字段和数据都纳入校验范围,不少实现漏掉了 seq,导致数据错位时校验仍然通过。
5.3 UDP 端口被占用,重启接收端报 address already in use
现象:快速反复实验时,用 Ctrl+C 杀掉上一个接收端后立刻重启,报OSError: [Errno 98] Address already in use。
原因:接收端进程虽然被杀掉了,但内核里的 UDP socket 可能还在完成关闭流程,或者上一次 fork 的子进程没回收干净。和 TCP 的 TIME_WAIT 不同,UDP 没有连接状态,但这个报错通常是上次进程没死透导致的。
解决:用ss -ulnp查端口占用,然后kill掉对应 PID。写启动脚本时,接收端启动前加一个重试循环,端口不可用时等 2 秒再试。如果代码是自己写的,可以给 socket 加SO_REUSEADDR选项,开发阶段能减少这个报错出现的频率,但不要因为设置了这个就放松对端口冲突的排查。
5.4 文件传完后 md5 一致,但中间有几秒明显停顿
现象:收发文件用 md5 对比完全一致,正确性没问题,但日志显示中间存在 2~3 秒的空白,用户直观感受是“卡了一下”。
原因:停等协议一次只允许一个包在途,每个包都要等 ACK 才能发下一个。本地环路延迟几毫秒,包大小 1024 字节,理论上限也只有几兆比特每秒,停顿是正常的。如果停顿时间超过一次正常 RTT,多半是超时设置与丢包率不匹配,导致某些包重传了但重传也丢了。
解决:先看两端日志里每个 seq 的重传次数。如果重传集中在某个 seq 上,且是固定范围,优先怀疑模拟信道的丢包逻辑写错了,把范围丢包当成随机丢包处理。再把丢包率从 0.05 降到 0.01 跑一遍,如果停顿消失,说明是参数给得太激进,而不是逻辑故障。
5.5 跟真实 TCP 程序互通完全不通
现象:拿着 RDT2.2 的收发器去连一个标准 TCP 服务,比如连一个 80 端口的 HTTP 服务,发出去的数据完全没有响应,抓包也看不到 TCP 三次握手。
原因:RDT2.2 的包格式是自定义的,端口号也没固定注册,抓包器只能看到 UDP,看不到“这是 RDT2.2”。它和 TCP 协议栈没有兼容关系,在 UDP 上自造了一套私有传输协议,只有两端都实现同一套规则才能对上。
解决:确认对端也是 RDT2.2 实现,并且字段序、校验和算法完全一致。哪怕两套 RDT2.2 实现,一个用大端一个用小端,都无法互通。真要跟真实 TCP 服务通信,应该走标准 socket 编程那条路,把 RDT2.2 只当作理解可靠传输的教具,别当成 TCP 的替代品。
6. 进阶:把 RDT2.2 改造成带滑动窗口的最小 TCP 原型
停等协议最大的瓶颈是用带宽换正确性,RTT 越大,吞吐越低。如果只是验证完正确性就结束,那 RDT2.2 的价值只发挥了一半。再往前走一步的常见做法,是把它扩成回退 N 帧(Go-Back-N)或者选择性重传,这一步做完,你就等于亲手搭了一个传输层协议的完整演化链。
改法并不复杂。发送端把WINDOW_SIZE从 1 改成 4,维护一个base和next_seq,允许连续发送窗口内的多个包,不用等每个 ACK。接收端还是只按顺序接收,乱序的后续包直接丢弃,并重发当前需要的 ACK。超时触发时,发送端重发从base开始的所有未确认包。这个策略跟 TCP 的快速重传相比当然粗糙,但足够让人理解一个简单窗口协议的行为。
验证时用tc命令在本地制造延迟和丢包,对比不同窗口大小下的文件传输时间。tc qdisc add dev lo root netem delay 100ms loss 5%改成不同的丢包率,跑同一份数据,基本能画出一条窗口大小与吞吐的关系曲线。这里有个真实 TCP 的类比值得记住:当你调 Windows 或 Linux 的 TCP 参数时,比如netsh int tcp set global timestamps=enabled这类操作,本质上也是在调整协议栈里和 RDT 模型类似的确认与时间戳机制。教学包教会你的那套“校验、序号、确认、重传”就是这些开关背后的理论骨架。
提示:实验结束记得用
tc qdisc del dev lo root清掉模拟配置,否则本地所有回环流量都会带着 100ms 延迟,连 SSH 登录都慢半拍。
我个人做这类实验时养成的习惯是:每次只改一个参数,记录吞吐和重传次数,改完就抓包存档一次。RDT2.2 的代码本来就不长,日志加得细一点,出问题半小时内能定位。别一次把窗口、超时、校验和算法全换了,那样出了问题根本分不清是哪个环节引入的,那种翻车我已遇过不止一次。把这个打包的实验从“跑通”做到“能调参”之后,你会发现自己对 TCP 连接的理解方式和光看原理完全不同。希望帮到你。
本文还有配套的精品资源,点击获取