ARTICLE DETAIL

资讯详情

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

TCP_RDT2.2.zip 实验环境:RDT 2.2 可靠传输状态机实现与调参

TCP_RDT2.2.zip 实验环境:RDT 2.2 可靠传输状态机实现与调参 简介TCP-RDT2.2.zip 是一份面向计算机网络课程学习者与实验实践者的可靠数据传输协议模拟资源围绕 RDT 2.2 版本展开重点解决 RDT 2.0 中 ACK 确认包可能位错、导致确认信息无法正确回传的问题。资源包共 15 个文件以 4 个 java 源文件与 4 个 class 编译文件为核心辅以 txt 日志、tcp 数据文件及 Eclipse 工程配置prefs、project、classpath、ini整体约 1.04MB结构紧凑便于直接导入 IDE 运行调试。已有 322 人学习下载。通过该实验读者可深入理解累计确认、超时重传与 ACK 纠错编码等机制掌握发送方与接收方的状态机设计、序列号与校验和配合方式并借助 recvData.txt、Log.txt 等文件观察数据收发过程排查位错与丢包场景下的协议行为适合作为 TCP 可靠传输原理的动手实践素材。1. 从 TCP_RDT2.2.zip 说起一个能跑起来的可靠传输实验环境很多人学完 TCP 三次握手、四次挥手背得出状态机但真让他写一个「发送方发数据、接收方回 ACK、ACK 丢了怎么办」的完整流程代码就卡住了。TCP_RDT2.2.zip 就是冲着这个断层来的——它把 RDT 2.2 的发送方、接收方、信道模拟、日志记录打包成一个可以直接导入 Eclipse 的 Java 工程让你在本地就能观察序列号翻转、ACK 位错、超时重传这些平时只存在于教材插图里的行为。这个包的核心价值不在于「又一个课程作业」而在于它把 ACK 出错这个容易被忽略的分支真正做进了代码里。RDT 2.0 只处理数据包损坏RDT 2.2 要处理的是「接收方回了 ACK但 ACK 在回程路上被打错了」——发送方收到一个语义错误的确认如果直接当成功处理数据就丢了如果当失败处理又可能重复发送。这个包用状态机把两种可能都覆盖了配合 Log.txt 能直接看到每次状态迁移的触发条件。适合谁用正在做计算机网络实验的学生、需要给学生演示可靠传输状态机的助教、以及想用 Java 快速验证自己 RDT 实现思路的开发者。工程结构不复杂src 下是源码bin 下是编译产物Config.ini 控制运行参数recvData.txt 是接收端落盘的数据ENCDA.tcp 是抓包记录。下面从工程结构开始拆把每个文件的作用、参数怎么调、跑起来之后看什么一步步说清楚。2. 拆开 TCP_RDT2.2.zip工程结构与 RDT 2.2 状态机实现2.1 目录里每个文件到底管什么解压之后看到的文件不是随便堆的每个都有明确分工。先看一张表把文件清单和职责对齐文件/目录类型作用src/com源码目录发送方、接收方、信道模拟、工具类bin/com编译输出Eclipse 自动生成的 .class 文件Config.ini配置文件窗口大小、超时时间、丢包率、错误率recvData.txt数据文件接收方成功交付后写入的明文数据ENCDA.tcp日志文件每次发送/接收/超时的原始记录Log.txt运行日志状态迁移和异常信息.project / .classpathEclipse 元数据导入工程时用不要手动改.settingsEclipse 配置JDT 编译级别、编码格式src/com 下面通常按功能分包发送方一个类、接收方一个类、信道一个类、主入口一个类。如果你打开 src 发现包名是 com.xxx.rdt 这种说明作者按标准 Java 包规范组织了代码导入 Eclipse 后直接能跑。Config.ini 是唯一需要你动手改的文件其他文件在运行过程中自动生成或更新。注意recvData.txt 和 ENCDA.tcp 在首次运行前可能是空的或者不存在这是正常的。接收方每正确交付一个数据段就往 recvData.txt 追加一行ENCDA.tcp 记录的是信道层面的收发事件两者对照着看能定位问题。2.2 RDT 2.2 状态机在代码里怎么落地RDT 2.2 和 2.0 最大的区别在发送方对 ACK 的处理。2.0 假设 ACK 不会出错收到就认为对方正确接收2.2 必须考虑 ACK 本身可能位错所以发送方的状态不能简单地在「等 ACK0」和「等 ACK1」之间跳还要处理「收到一个无法解析的 ACK」这种情况。常见做法是给每个状态配一个超时定时器超时后重传当前数据段同时序列号不变。接收方那边则要判断收到的数据段序列号是不是期望的校验和对不对如果都对交付数据、回 ACK、翻转期望序列号如果数据段损坏但序列号是期望的回一个 NAK 或者重复上一个 ACK如果序列号不是期望的说明是重复包直接丢弃但重发上一个 ACK。下面这段伪代码展示了发送方的核心循环逻辑你可以对照 src 里的实际实现看差异// 发送方主循环简化版对应 RDT 2.2 发送端状态机 while (true) { // 从上层取一个数据段附上当前序列号 seq Packet pkt makePacket(data, seq); // 计算校验和写入包头 pkt.checksum computeChecksum(pkt); // 通过信道发送 channel.send(pkt); // 启动超时定时器超时时间从 Config.ini 读取 startTimer(timeoutMs); // 等待 ACK 或超时 Event e waitForEvent(); if (e.type TIMEOUT) { // 超时重传同一个包序列号不变 continue; } if (e.type ACK_RECEIVED) { // 校验 ACK 包的校验和 if (!verifyChecksum(e.packet)) { // ACK 损坏当作没收到继续等超时 continue; } // ACK 有效检查确认的序列号 if (e.packet.ackSeq seq) { // 确认的是当前包翻转序列号取下一个数据 seq 1 - seq; stopTimer(); break; // 跳出等待取下一个数据 } else { // 确认的是上一个包说明是重复 ACK忽略 continue; } } }逻辑说明这段代码的关键在于verifyChecksum失败时没有直接重传而是继续等待超时。这是 RDT 2.2 的一个典型处理方式——ACK 损坏时发送方无法判断接收方到底收没收到贸然重传可能导致接收方收到重复包但接收方有序列号去重机制所以重传是安全的只是效率低一点。参数方面timeoutMs从 Config.ini 读取通常设为平均 RTT 的 2 到 3 倍seq只有 0 和 1 两个值这是停等协议的特征。接收方的逻辑对应着写维护一个expectedSeq初始为 0。收到包先验校验和坏了就回 NAK 或重复上一个 ACK校验和对了再看序列号等于expectedSeq就交付、回 ACK、翻转expectedSeq不等于就丢弃但重发上一个 ACK。这个「重发上一个 ACK」的动作就是 RDT 2.2 处理重复包的核心手段。2.3 Config.ini 里哪些参数真正影响行为Config.ini 是调参入口但不是什么都能改。下面这几个参数是实际运行中会直接影响状态机行为的# Config.ini 关键参数示例字段名以实际文件为准 timeout_ms2000 # 超时重传时间单位毫秒 loss_rate0.1 # 数据包丢失概率0 到 1 之间 corrupt_rate0.05 # 数据包位错概率0 到 1 之间 ack_corrupt_rate0.05 # ACK 包位错概率单独控制 window_size1 # 停等协议固定为 1不要改timeout_ms设得太小会导致大量不必要的重传设得太大则丢包后恢复慢。我一般会先设成 2000跑一轮看 Log.txt 里超时次数如果超时次数远大于丢包次数说明超时太短了。ack_corrupt_rate是 RDT 2.2 特有的参数把它调高到 0.2 以上你能明显看到发送方反复重传同一个包而接收方因为序列号去重不会重复交付数据——这就是 RDT 2.2 比 2.0 多出来的那层保护。loss_rate和corrupt_rate的区别要分清丢包是包直接没了接收方什么都收不到位错是包到了但内容变了接收方校验和不过。两者对状态机的影响不同丢包只能靠超时发现位错可以靠校验和立即发现并回 NAK。把这两个参数分别调大观察 Log.txt 里事件类型的分布能帮你理解两种错误的不同恢复路径。3. 跑起来从导入 Eclipse 到观察第一次超时重传3.1 导入工程与编译路径确认这个包是标准 Eclipse Java 工程导入方式不复杂但有几个地方容易翻车。第一步打开 Eclipse选择 File → Import → General → Existing Projects into Workspace然后选中解压后的 TCP_RDT2.2 目录。Eclipse 会自动读取 .project 和 .classpath把 src 标记为源码目录bin 标记为输出目录。导入后如果项目图标上出现红色感叹号通常是 JRE 版本不匹配。右键项目 → Properties → Java Build Path → Libraries看 JRE System Library 是不是显示为 unbound。如果是双击它改成你本机安装的 JDK 版本。这个工程一般用 Java 8 或 11 就能跑不需要额外依赖库。编译路径确认完之后在 src 下找到包含 main 方法的类右键 → Run As → Java Application。如果控制台开始输出发送方和接收方的交互日志说明编译和运行环境没问题。如果报 ClassNotFoundException检查 bin 目录下有没有对应的 .class 文件没有的话执行 Project → Clean 重新编译。3.2 用 Config.ini 构造一个「ACK 必丢」的场景想快速看到 RDT 2.2 的超时重传行为最直接的办法是把 ACK 丢失概率拉高。修改 Config.initimeout_ms1500 loss_rate0.0 corrupt_rate0.0 ack_corrupt_rate0.0 ack_loss_rate0.5 # 如果配置文件支持单独控制 ACK 丢失如果 Config.ini 里没有ack_loss_rate这个字段就把loss_rate设成 0.3这样数据包和 ACK 都有概率丢。运行之后打开 Log.txt你会看到类似这样的记录[SENDER] send pkt seq0, checksum0x3A [SENDER] timer started, timeout1500ms [SENDER] timeout! retransmit pkt seq0 [SENDER] send pkt seq0, checksum0x3A [RECEIVER] recv pkt seq0, checksum ok [RECEIVER] deliver data, send ACK seq0 [SENDER] recv ACK seq0, checksum ok [SENDER] seq flip to 1这段日志里第一次发送后超时了发送方重传了同一个包seq 仍然是 0接收方收到后交付数据并回 ACK发送方这次收到了才翻转序列号。整个过程里接收方只交付了一次数据说明去重机制生效了。3.3 对照 ENCDA.tcp 和 recvData.txt 验证正确性Log.txt 是应用层视角ENCDA.tcp 是信道层视角recvData.txt 是结果视角。三个文件对照着看能确认实现是否正确。ENCDA.tcp 里记录的是每次信道传输的原始事件包括发送时间、包序号、校验和、是否被丢弃或篡改。如果你看到某个包在 ENCDA.tcp 里出现了两次发送记录但 recvData.txt 里只多了一行数据说明重传没有导致重复交付接收方的序列号去重逻辑是对的。recvData.txt 的内容应该是发送方原始数据的完整副本顺序一致没有重复行没有缺失行。如果发现某一行数据重复了说明接收方的expectedSeq翻转逻辑有问题如果发现某一行缺失说明发送方在收到有效 ACK 后没有正确取下一个数据段。提示跑完一轮之后先把 recvData.txt 清空再跑第二轮否则两次运行的数据会混在一起不好判断哪次出了问题。ENCDA.tcp 和 Log.txt 同理建议每次运行前备份或清空。3.4 调参观察不同错误率下的状态迁移次数把corrupt_rate从 0 逐步调到 0.3每次跑 100 个数据段统计 Log.txt 里timeout和checksum fail出现的次数。你会看到一个趋势位错率低的时候大部分错误靠校验和立即发现并回 NAK 恢复位错率高到一定程度ACK 本身被篡改的概率也上升发送方开始频繁超时重传。这个实验能帮你建立对 RDT 2.2 性能边界的直觉它不是万能的当信道错误率高到 ACK 频繁损坏时停等协议的效率会急剧下降。这也是为什么真实 TCP 要引入滑动窗口、快速重传、选择性确认等机制——RDT 2.2 是理解这些机制的基础但不是终点。4. 避坑与排查五个让实验跑不通的常见问题4.1 导入后 src 下没有包结构只有一堆 .java 文件现象Eclipse 里 src 显示为普通文件夹Java 文件图标没有变成蓝色 J右键也没有 Run As → Java Application。原因.classpath 文件丢失或内容被改过Eclipse 没有把 src 识别为源码目录。解决右键项目 → Properties → Java Build Path → Source点 Add Folder勾选 src确定后 Eclipse 会重新索引。如果 .classpath 文件还在但内容不对可以手动编辑确保有classpathentry kindsrc pathsrc/这一行。4.2 运行时报「端口被占用」或「地址已在使用」现象控制台输出 BindException: Address already in use。原因上一次运行的进程没有正常退出Socket 还占着端口。这个工程如果用了真实 Socket 模拟信道端口冲突很常见。解决在任务管理器里找到残留的 java.exe 进程结束掉或者修改 Config.ini 里的端口号换一个。更稳妥的做法是在代码里给 ServerSocket 设置setReuseAddress(true)这样即使上次没正常关闭也能立即重用端口。4.3 Log.txt 里全是超时但 recvData.txt 一行数据都没有现象发送方反复超时重传接收方似乎完全没收到。原因接收方没有启动或者接收方监听的端口和发送方发送的端口不一致。解决确认运行顺序是先启动接收方再启动发送方。检查 Config.ini 里发送方和接收方的端口配置是否一致。如果代码里端口是硬编码的打开发送方和接收方两个类确认sendPort和listenPort是同一个值。4.4 ACK 校验和总是失败但数据包校验和正常现象接收方正常交付数据并回 ACK发送方收到 ACK 后校验和不通过继续等待超时。原因ACK 包的校验和计算范围和数据包不一致。常见错误是发送方计算 ACK 校验和时包含了序列号字段而接收方计算时没包含或者反过来。解决打开发送方和接收方里计算校验和的函数确认两边对 ACK 包的计算范围完全一致。通常校验和只覆盖包头固定字段加数据部分序列号和确认号是否参与计算要两边对齐。这个坑很隐蔽因为数据包校验和正常说明基本逻辑没问题只有 ACK 路径上不一致。4.5 程序跑完后 recvData.txt 有数据但顺序和发送顺序不一致现象接收方交付的数据行顺序乱了比如先出现第 3 段再出现第 1 段。原因接收方没有严格按expectedSeq判断或者多线程环境下写文件没有加锁。解决检查接收方的序列号判断逻辑必须是「只有序列号等于 expectedSeq 才交付并翻转」。如果用了多线程给写 recvData.txt 的操作加 synchronized 块。停等协议本身是串行的如果代码里引入了并发反而会破坏顺序保证。5. 进阶把 RDT 2.2 改成滑动窗口先验证这三个指标停等协议跑通之后最自然的想法是把它扩展成滑动窗口。但直接改代码容易乱我一般会先加三个统计指标用数据判断当前实现离滑动窗口还有多远。第一个指标是信道利用率。在 Log.txt 里记录每次发送和收到 ACK 的时间戳算出发送方处于「等待 ACK」状态的时间占比。停等协议下这个占比接近 100%因为发送方大部分时间都在等。如果这个值低于 80%说明超时设置或者错误恢复逻辑有优化空间。第二个指标是重传率。统计retransmit事件次数除以总发送次数。在 loss_rate0.1 的场景下理想重传率应该在 10% 到 15% 之间。如果超过 30%说明超时太短或者 ACK 处理逻辑有重复触发。第三个指标是接收方去重率。统计接收方收到但丢弃的重复包数量除以总接收包数。这个值应该和重传率接近如果明显偏低说明发送方重传的包接收方没收到可能是信道模拟的丢包逻辑有问题。// 在发送方和接收方各加一个计数器运行结束后打印 class Stats { static int totalSent 0; static int totalRetransmit 0; static int totalRecv 0; static int totalDuplicate 0; static void report() { double retransRate totalSent 0 ? 0 : (double) totalRetransmit / totalSent; double dupRate totalRecv 0 ? 0 : (double) totalDuplicate / totalRecv; System.out.println(retransmit rate: retransRate); System.out.println(duplicate rate: dupRate); } }把这三个指标跑出来之后再动手改滑动窗口就有依据了。滑动窗口的核心是把「发送一个包就等 ACK」改成「连续发多个包用累计确认推进窗口」。接收方那边要把expectedSeq从单个值改成窗口基序号并且增加乱序缓存。这些改动在 RDT 2.2 的代码结构上都能找到对应的扩展点但前提是你先把停等版本的每个状态迁移都跑通了。从那以后我每次拿到这类协议实验包都强制自己先跑一轮默认配置把 Log.txt 和 recvData.txt 对照着看一遍确认状态机没有死循环、没有重复交付再动手改参数或扩展功能。这个习惯帮我省了很多「改了半天发现是原始代码就有问题」的时间。希望帮到你。本文还有配套的精品资源点击获取
返回列表