首页 / 资讯中心 / 文章详情

TCP拥塞控制Reno版本NS2仿真实验:从cwnd曲线到快速重传全解析

TCP拥塞控制Reno版本NS2仿真实验:从cwnd曲线到快速重传全解析 ★ FEATURED ARTICLE
简介这是中国海洋大学计算机网络课程中TCP Reno版本实验的配套资源包适合高校网络课程学生或对TCP拥塞控制算法感兴趣的初学者。包内含完整的实验工程与说明覆盖快速重传、快速恢复、拥塞窗口调整等核心机制并配有可运行代码与实验分析文档便于对照学习。资源共135个文件以84个HTML说明/报告页面、Java与class源码为主另有XML配置、jar依赖及少量文本记录整体仅1.48MB结构清晰适合直接导入集成开发环境或用作实验参考。已有801人浏览学习口碑较佳。对想深入理解TCP Reno从理论到实现、以及如何通过模拟网络观察丢包与拥塞行为的读者来说这是一份能够快速上手、兼具代码与讲解的实用资料。1. 计算机网络实验里的Reno版本为什么它是拥塞控制的“基准盘”计算机网络实验里TCP拥塞控制中的Reno版本几乎是每个学生都绕不开的“基准算法”。某高校的课程实验要求自己搭一个网络仿真观察Reno的拥塞窗口cwnd在慢启动、拥塞避免、快速重传和快速恢复几个阶段里怎么变化再和Tahoe版本做对比。这个实验看起来只是跑一个脚本实际上要踩的坑不少没丢包时曲线单调上升看不到恢复过程、丢包太猛又触发超时导致算法退化、画出来的图和理论对不上。这篇笔记把我自己复现这套实验的完整路径写出来从选NS2还是ns-3、最小脚本怎么写、cwnd曲线怎么画到三组常见的翻车现场以及最后怎么用对比实验验证你已经跑对了方向。适合正在做课程实验、或者想彻底搞懂Reno版本行为的人照着做一遍。2. Reno算法在NS2里长什么样原理与实验选型2.1 从Tahoe到Reno一次丢包后的两种反应Reno版本的核心价值在于它把“丢包后怎么办”这件事改得更聪明了。早期的Tahoe算法收到三个重复ACK后把ssthresh设为当前cwnd的一半然后直接把cwnd重置为1从头开始慢启动。这样做保守但代价是链路立刻空出来后吞吐量塌方。Reno版本在Tahoe的基础上补上了快速恢复收到第三个重复ACK时进入快速重传把cwnd减半并加上3个报文段此后每收到一个重复ACK就把cwnd临时增加一段直到ACK确认了新数据再退出快速恢复回到拥塞避免阶段。课程实验让你观察Reno版本本质上就是让你亲眼看到这几个状态切换。你需要在仿真里制造一次丢包然后从cwnd时间序列里读出丢包前是指数增长的慢启动还是线性增长的拥塞避免丢包后是cwnd减半还是直接归零。只看教科书上的伪代码很难有体感跑一次NS2的trace曲线比背十遍状态机都管用。很多同学把Tahoe和Reno跑成了同一条线就是因为没有制造出足以触发快速重传的重复ACK最后都走了超时重传路径算法差异全被掩盖了。这里还要提一下“快速恢复”的触发条件。它依赖接收方收到乱序包后立刻回复重复ACK也就是所谓的“报文段已经离开网络但它后面的包还在路上”。所以链路带宽延迟积BDP太小、窗口还没打开几个段就丢包时即使丢了包也凑不齐三个重复ACKReno版本只能超时。这也是实验里“丢包率不是越高越好”的根源。2.2 为什么课程实验几乎都用NS2而不是ns-3我接触到的课程实验资料绝大多数都是NS2版本。原因很实际NS2用Tcl写仿真脚本几十行就能跑通一个Reno实验ns-3虽然更现代但拓扑、协议栈、应用层的配置要写C或Python对一个只想看cwnd曲线的课程作业来说学习成本高到不值。NS2里Agent/TCP/Reno和Agent/TCPSink都已经封装好你要做的只是创建三个对象两个节点、一条带丢包的双向链路、一个FTP流。另一个选NS2的理由是trace格式足够简单。NS2的trace文件每一行都是约10个字段自己用awk就能把吞吐量、丢包数、RTT全部抽出来这对于实验报告里的数据分析环节友好。ns-3的标准trace更结构化但很多人第一步都被它的事件系统和命令行参数折腾得够呛。你如果只想快速得到一条可以放进实验报告的cwnd曲线NS2是最稳的路径。不过我也要提醒一句NS2的安装是第一个大坑。它依赖的OTcl/Tk版本都比较老在较新的Linux发行版上编译时容易报错。常见的做法是装一个2.35版本的虚拟机镜像或者用Docker拉现成的NS2镜像省去编译依赖的时间。如果你在Windows上做我的建议是不要直接在Windows下硬折腾用WSL2里跑纯命令行模式就够了反正我们的实验只需要ns和gnuplot不依赖NAM图形窗口。3. 写一个最小Reno仿真脚本Tcl代码与参数说明3.1 拓扑、代理和FTP流最小脚本骨架下面这段脚本是我做这个实验时最常用的一套骨架它搭建了一个两节点拓扑节点0向节点1发送FTP数据发送方的TCP代理显式指定为Reno版本。链路带宽设成1Mb、延迟50ms队列类型DropTail并把队列上限设为10个包。链路丢包率我用ErrorModel单独挂上去而不是直接在链路参数里写这样方便后续做多个实验时只改一个值。# reno.tcl - 最小TCP Reno拥塞控制仿真脚本 set ns [new Simulator] # 打开trace文件记录所有网络事件 set tracefile [open reno.tr w] $ns trace-all $tracefile # 创建两个节点 set n0 [$ns node] set n1 [$ns node] # 双向链路1Mb带宽50ms传播延迟DropTail队列 $ns duplex-link $n0 $n1 1Mb 50ms DropTail # 队列长度上限为10个包 $ns queue-limit $n0 $n1 10 # 丢包模块0.01的随机丢包率按包丢弃 set loss [new ErrorModel] $loss set rate_ 0.01 $loss set unit_ pkt $loss set eventtype_ Packet $ns lossmodel $loss $n0 $n1 # 发送方显式使用Reno版本而不是默认的Tahoe set tcp [new Agent/TCP/Reno] $tcp set fid_ 1 $tcp set packetSize_ 1460 $ns attach-agent $n0 $tcp # 接收方TCPSink接收端负责回ACK set sink [new Agent/TCPSink] $ns attach-agent $n1 $sink $ns connect $tcp $sink # 应用层FTP无限流从0.1秒开始10秒停止 set ftp [new Application/FTP] $ftp attach-agent $tcp $ns at 0.1 $ftp start $ns at 10.0 $ftp stop # 仿真结束时间留0.5秒让队列里的包清空 $ns at 10.5 exit 0 $ns run这段脚本里最关键的一行是set tcp [new Agent/TCP/Reno]。如果只写Agent/TCPNS2里默认的是Tahoe行为那你后面画出来的曲线就是Tahoe版本实验结论全错。$tcp set fid_ 1用来标记流ID后面用awk统计吞吐量时靠这个字段过滤出属于本实验TCP流的数据包。packetSize_设为1460字节这是常见的MSS取值它决定了窗口的“段数”含义也影响BDP的计算。ErrorModel挂在双向链路上方向是从n0到n1。rate_ 0.01表示每个包有1%的概率被丢弃unit_ pkt表示丢包单位是包而不是时间间隔。这个值是我调整过比较稳的起步值既能让trace里出现不少d事件又不会因为丢包太密集导致TCP频繁超时。队列上限10个包对这个BDP约6.25个段来说够用窗口展开后能攒出足够的在途包来触发快速重传。3.2 记录cwnd的两个关键动作只跑脚本只能得到trace文件拿不到cwnd的逐时刻值。我通常在同一个脚本里加一段记录过程每隔0.05秒采集一次当前发送方的拥塞窗口追加写入cwnd.dat。两个关键动作是定义record过程时用global声明变量以及用$ns at一次性调度后让过程自己递归调度自己。# 打开cwnd数据文件 set cwfile [open cwnd.dat w] # 记录过程每0.05秒写一行 时间 cwnd proc record {} { global ns tcp cwfile set now [$ns now] set cwnd [$tcp set cwnd_] puts $cwfile $now $cwnd $ns at [expr $now 0.05] record } # 在TCP开始发送前启动记录 $ns at 0.05 record$tcp set cwnd_是直接读取OTcl实例的内部变量cwnd_单位是报文段数不是字节。有人会在这里踩坑以为输出值和Wireshark里看到的字节窗口一致实际差了一个packetSize_的倍数。如果你想同时记录ssthresh可以把puts那行改成puts $cwfile $now $cwnd [$tcp set ssthresh_]这样画图时就能看到慢启动阈值的变化线条。采样间隔0.05秒是经验值。间隔太大会漏掉快速恢复期间cwnd的细节比如Reno在重复ACK阶段每段增加的那一格间隔太小cwnd.dat会膨胀到几千行也看不出整体趋势。如果你之后要对曲线做平滑处理建议在画图时对数据做滑动平均而不是把采样间隔调得更密。还有一点record过程在10.5秒exit 0触发前会一直调度下去所以不需要手动停止仿真结束自然就被截断。4. 跑通并抽数据从trace文件到cwnd曲线4.1 用awk和gnuplot把曲线画出来脚本保存为reno.tcl后在终端里执行ns reno.tcl。如果一切正常你会看到目录下出现reno.tr和cwnd.dat。cwnd.dat已经是两列纯文本可以直接给gnuplotreno.tr则是用来校验“到底有没有发生丢包、丢包后有没有走快速重传”的原始证据。# 统计trace中接收方收到的属于流ID1的字节总数并估算吞吐量 awk $1r $81 {sum $6} END {printf throughput: %.2f Kbps\n, sum*8/9.9/1000} reno.tr# cwnd.plot set terminal png size 800,500 set output cwnd.png set title TCP Reno Congestion Window set xlabel Time (s) set ylabel cwnd (packets) plot cwnd.dat with lines title cwnd运行gnuplot cwnd.plot后得到png图片。这里的awk对NS2标准trace字段做了假设$1是事件类型r表示接收$6是包大小$8是流ID。这和我们脚本中$tcp set fid_ 1对应。9.9是FTP实际发送时长从0.1到10秒除以后得到平均吞吐量。画出来的cwnd曲线理论上应该有三种典型形态前几秒快速上升的慢启动段到达阈值后转为线性爬坡丢包发生后cwnd骤降到一半再快速恢复。如果曲线几乎是一条直线升到很高说明没有丢包发生如果曲线反复跌到1然后重新指数爬升说明丢包全部触发了超时而非快速重传。这两种都说明参数没调对后面第五章会讲排查方法。4.2 丢包率怎么设数值与理论带宽的关系丢包率是这个实验里最敏感的参数。很多人贪方便直接设0.05甚至0.1结果Reno版本的曲线完全看不出快速恢复全是超时。原因在于TCP的快速重传需要三个重复ACK而重复ACK的数量取决于丢包后还有多少个在途包能回来。我们的链路是1Mb带宽、50ms单向延迟RTT约100ms带宽延迟积BDP 1Mbps × 0.1s ÷ (1460×8) ≈ 8.6段。也就是说发送方窗口撑到8段左右时链路上最多同时有8个包在飞。这时随机丢掉1个包接收方最多能产生7个重复ACK足够触发快速重传。如果丢包率太高比如0.05那么在窗口还没长大到BDP之前就可能连续丢多个包重传定时器超时Reno版本退化成和Tahoe一样的慢启动快速恢复完全展现不出来。我的建议是丢包率先在0.01试看曲线有没有“缺口”。如果丢包发生得太早cwnd还没爬到BDP就再减小到0.005或者推迟丢包时间。你还可以用ErrorModel的burst_参数制造“短时间内连丢两个包”专门观察Reno在连续丢包时快速恢复失效的现象这个在第六章会讲。# 检查trace中丢包事件的位置和数量 grep ^d reno.tr | wc -l grep ^d reno.tr | head -5这行命令能快速确认丢包是否真实发生。如果d事件数量为0说明ErrorModel没挂上或者丢包率太低、仿真时间太短。注意NS2的trace里d事件表示队列丢弃是发送端看得到的丢包如果包在接收端被检测为失序但没被丢弃trace里只会有乱序标识不会出现d事件这是正常的。5. Reno实验避坑记录6个常见问题与排查方法5.1 脚本跑不起来的3个坑坑1终端输入ns reno.tcl直接报command not found。现象是NS2已经装好但可执行文件路径不在PATH里。原因是安装时没有把ns所属的bin目录加入环境变量。解决先找到ns位置比如which ns或ls /usr/local/ns-allinone-2.35/bin/ns然后export PATH$PATH:/usr/local/ns-allinone-2.35/bin。如果你用的是别人给的脚本启动ns那就要检查脚本里的路径是不是写死了某台机器上的目录。坑2运行后错误提示unknown procedure new或cant read ns。现象是脚本在set ns [new Simulator]处报错或者第一行就被卡住。原因往往是脚本文件保存时带了BOM头或者被Windows换行符污染Tcl解释器把第一行当成了乱码。解决用sed -i s/\r$// reno.tcl清理回车或者直接在Linux下用vim重新保存。还有一种情况是脚本里用了中文注释但文件编码不是UTF-8也会让Tcl解析报错我一般全用英文注释。坑3挂ErrorModel时提示invalid command name lossmodel。现象是脚本执行到$ns lossmodel $loss $n0 $n1时崩溃。原因不是命令不存在而是$loss变量没正确创建或者NS2的Simulator类缺少对应方法极少见多发生在非常旧的版本。解决确认set loss [new ErrorModel]这行没有拼错并且$ns lossmodel的语法确实是“第一个参数是ErrorModel对象后面两个是节点”。如果还不行检查你的NS2是否用的是别人裁剪过的版本换标准2.35即可。5.2 曲线不对的3个坑坑4cwnd曲线是一条没有下落的直线直到实验结束都保持高位。现象是图上完全看不到快速恢复的锯齿只有慢启动和拥塞避免。原因是ErrorModel没有生效可能rate_设成了0或者丢包模块挂载之后没有调用$ns run之前的$ns lossmodel也可能是$tcp set fid_ 1没有生效导致你统计的cwnd不是这条流。解决先用grep ^d reno.tr | wc -l确认丢包事件数量不为0再看是不是丢包率太高导致窗口从未打到BDP所有丢包都超时曲线跌到1后重来。调整方向是把丢包率降到0.005到0.01之间或者把链路带宽提高到2Mb。坑5曲线在丢包后一路跌到1随后又指数猛爬完全不像是Reno的“降到一半”。现象是快速重传没发生走了超时重传路径。原因有两种一是丢包时窗口太小发出的在途包不足三个接收方凑不够重复ACK二是丢包率太高三个重复ACK中间又丢了包重传定时器先到期。解决把丢包率降下来并且让FTP开始时间提前比如从0.05秒就启动使cwnd有足够时间在第一个丢包前长到8段以上。你也可以在trace里检查是否有三个连续的d事件之间夹杂着r事件确认重复ACK存在。坑6把发送方的代理改成Agent/TCP后跑出来的曲线和Agent/TCP/Reno一模一样。现象是课程要求做Tahoe和Reno的对比实验但你得到两条完全重叠的线。原因是NS2里Agent/TCP本身默认就是Tahoe行为而你对比的时候可能两个脚本都用了同一个类名还有一个隐蔽原因是两个脚本虽然用了不同类名但丢包率或随机数种子不同导致一个触发了RTO一个触发了快速重传结果从曲线形状上看反而差不多。解决明确对比脚本A用Agent/TCP脚本B用Agent/TCP/Reno保证除了代理类名以外所有拓扑、丢包率、FTP时间都完全一致同时把丢包率调到能稳定触发快速重传的区间并固定随机数种子让两个脚本走同一条丢包序列。# 在脚本开头固定随机数种子保证两次对比实验的丢包序列一致 global defaultRNG $defaultRNG seed 12345这行代码是NS2里固定随机数种子的常见写法。不加的话两次运行的随机丢包位置不同对比实验就变成了“运气对比”。注意不同NS2版本对RNG类名和方法有差异如果是2.35上面的写法通常有效。6. 把Reno实验做深一步用对比实验验证结果验证Reno版本没有跑偏最直接的方法是做一个Tahoe/Reno双跑对比并故意在特定时刻制造一个单包丢失。下面这张表总结了两种算法在相同丢包条件下的预期行为我建议你在写实验结论前先按这个框架自检。观察点TahoeReno收到3个重复ACK后cwnd设为1重新慢启动设为ssthresh3进入快速恢复快速恢复期间cwnd不存在每收到一个重复ACK加1段恢复后进入的阶段慢启动拥塞避免连续丢2个包第二次超时可能性高可能退化为超时这点和Tahoe会重合进阶做法是把ssthresh_初始值设成一个很小的数字比如4强迫发送方在慢启动阶段很快进入拥塞避免。这样你能在同一条曲线上看到更短的慢启动、更长的线性爬坡实验报告里更容易区分两个阶段。我用$tcp set ssthresh_ 4跑过一次曲线从第1秒开始就在做线性增长配合0.05秒采样能清楚看到每次RTT加1段的阶梯感那是Reno版本拥塞避免最典型的证据。另一个我常用的验证技巧是把采样间隔改成0.02秒然后放大快速恢复区域看cwnd是否有“一加一减”的毛刺。快速恢复期间每收到一个重复ACKcwnd临时增加一段这是Reno版本区别于所有其他版本的指纹。如果这段没有毛刺说明你看到的只是单纯的超时重传实验完全失败。我自己的血泪经验是一开始太贪心丢包率直接设0.05结果整个窗口一直在1到3之间震荡曲线像梳子齿根本没法讲清楚任何算法行为。后来老老实实把丢包率降到0.01并把FTP起始时间提前到0.05秒才第一次在trace里同时看到了慢启动、拥塞避免、快速重传和快速恢复四个阶段的完整序列。做这种仿真实验大部分时间不花在写脚本上而是在反复调参数、看trace、再调参数的循环里。希望这篇笔记能帮你跳过那些我已经踩过的坑把时间省下来真正读懂Reno版本的每一次cwnd升降。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站