NS2代码再挖掘:从tcl仿真到awk结果提取的完整实践
2026/9/24 0:37:16 网站建设 项目流程

简介:这是一份面向NS2入门者与网络仿真研究者的代码包,聚焦网络协议仿真、路由算法、移动模型与性能统计等核心场景。通过28个文件、623KB的紧凑组织,读者可直接运行Tcl脚本观察TCP拥塞控制、DSDV路由决策和Random Waypoint移动节点的行为,并借助nam动画、tr轨迹与awk统计脚本完成仿真结果分析;资源包含8个Tcl配置脚本、2个C++源码及头文件、nam/tr结果文件以及gnuplot绘图脚本,覆盖从基础网络拓扑搭建到OOPSI协议扩展的完整链条。已有222人学习下载,内容按章节与实验组织,从第二章基础示例到第六章xgraph绘图,再到第七章mflood源码剖析,配合示例逐步演示数据包发送、路由转发、移动场景建模与性能指标提取。学习者既能理解NS2的事件驱动机制,也能通过修改参数和脚本,自主开展协议对比与网络性能实验。

1. ns2.rar 里的 NS2 代码:过时仿真器的最后一块价值洼地

ns2.rar 这个包名,在压缩包分享时代被研究生们传来传去,里面装的几乎都是 NS2 相关代码:tcl 仿真脚本、nam 动画文件、trace 结果,运气好的还会附带 C++ 扩展源码。NS2 的正式版停在 2.35 不再更新,但网络仿真论文里的对比实验,至今大量引用这套代码。

我为什么还推荐去解压这种老包?因为论文里讲不清楚的算法细节,往往只能从可运行的示例代码里倒推;而 NS3 的 API 演进报错太多,和旧论文结果对齐非常难。NS2 代码简单、依赖少,适合做路由协议和无线网络的基础验证,也适合作为理解离散事件仿真的入门标本。

接下来我会按平时带师弟的顺序走一遍:先拆开压缩包看文件布局,再用最小 tcl 脚本跑通链路,然后从 trace 提取论文指标,最后改 C++ 源码做自定义协议。新手能跟上,熟手可以跳过前四章直接看避坑和改码。

2. 拆开 ns2.rar:搞明白 NS2 代码的四种文件与两层架构

2.1 解压后最常见的四类文件:tcl、nam、tr、C++

从网上或者老师 U 盘拿到的 ns2.rar,解压后通常不是一整个 ns-allinone 安装目录,而是一个实验文件夹。里面最常见的是四类文件,我先花两分钟把这四类东西的职责说清楚,因为后面对代码的操作都会落在它们身上。

第一类是 tcl 脚本,也就是扩展名为 .tcl 的仿真入口文件。NS2 里所谓“跑代码”,绝大多数时候就是执行一条 ns xxx.tcl 命令。tcl 脚本负责创建节点、建立链路、挂载协议栈、调度事件,最终把运行过程写进 trace 文件。第二类是 nam 文件,扩展名为 .nam,它是 nam 可视化工具读取的动画记录,包含节点的位置、链路状态和分组流动轨迹。答辩的时候打开 nam 演示比念数据直观得多。

第三类是 tr 文件,也就是 trace 日志。ns 运行时把每个分组事件的细节按行追加到这个文本文件里,吞吐量、丢包率、端到端延迟都是从它里边算出来的,这是论文数据真正的来源。第四类是可选的 C++ 源码,包括 .cc 和 .h 文件,当你需要修改协议核心逻辑、增加新的 Agent 类时才会用到它们。

我不建议一上来就把 tcl 文件拖进文本编辑器乱改。先把所有文件放进同一个纯英文路径,比如 D:\ns2lab 或者 ~/ns2lab,目录名不要有中文和空格。NS2 是老代码,对路径里的特殊字符处理很差,中文路径经常导致 nam 和 trace 输出异常,这是第一个不需要调试就能避开的坑。

2.2 OTcl 与 C++ 两层架构:为什么改 NS2 代码要分清编译层和解释层

NS2 代码看起来只有 tcl 脚本,但它的底层是 C++ 和 OTcl 两套体系缝合出来的。理解不了这一层,后面改协议时会彻底抓瞎,因为你会发现“改了一个参数没反应”或者“加了新 Agent 却无法实例化”。

C++ 层负责时间关键的部分,比如分组格式、队列管理、路由查找、调度器事件循环。这些代码在 ns 启动之前就已经编译成二进制了,运行效率高,但不能在脚本里动态改动。OTcl 层负责配置和组装,脚本里写的 set ns [new Simulator]、$ns duplex-link 这类语句都由 OTcl 解释器执行,它把 C++ 对象包装成 tcl 对象,让你能像搭积木一样组织仿真场景。

常见的做法是:C++ 写协议行为,OTcl 写场景配置。一个对象要被脚本使用,必须经过 TclClass 注册,比如 C++ 里的类经过注册后才能在 tcl 里写成 Agent/UDP。你从 ns2.rar 里看到的 tcl 脚本只是冰山一角,真正的协议行为在对应目录的 C++ 文件里。

这就解释了为什么改 NS2 代码有两种完全不同的路线。只调链路带宽、流量速率、节点数量,属于配置层改动,不重新编译;要改路由协议字段、增加新的控制报文、改变邻居发现逻辑,属于核心层改动,必须动 C++ 源码然后 make。分清这条界线,可以省掉一大半白费的力气。

2.3 环境准备:把 ns 命令装进 PATH,并验证它真的可用

从 ns2.rar 里拿到的散件通常只是实验用脚本,真正执行它们还需要一套能运行的 NS2 环境。ns-allinone-2.35 是最常见的安装包,解压完会有 ns-2.35、nam、xgraph、tcl8. x 等子目录。装好之后最重要的事是设置环境变量,让系统知道 ns 命令去哪找。

我一般会在 ~/.bashrc 里追加下面的内容,路径按实际解压目录修改:

export NS_HOME=$HOME/ns-allinone-2.35 export PATH=$NS_HOME/bin:$NS_HOME/tcl8.5/unix:$NS_HOME/tk8.5/unix:$PATH export LD_LIBRARY_PATH=$NS_HOME/tcl8.5/unix:$NS_HOME/tk8.5/unix:$NS_HOME/otcl-1.14:$NS_HOME/lib:$LD_LIBRARY_PATH

逻辑说明:NS_HOME 指向安装根目录,后续所有路径都以它为基准,避免在多个目录间来回切换时打错绝对路径。PATH 里加的是 bin、tcl 和 tk 的 unix 子目录,这样 ns、nam、xgraph 这些命令才能被直接调用。LD_LIBRARY_PATH 是 Linux 下最容易被忽略的一项,NS2 依赖 otcl 和 tk 的动态库,不加它运行时经常报找不到 libtcl8.5.so 或者 libotcl.so。

参数说明:你机器上解压出来的 tcl 版本号不一定正好是 8.5,先 ls 看一下把版本号替换进去;tcsh 用户改成 setenv 写法,原理相同。修改完执行 source ~/.bashrc 让配置生效,然后运行命令验证。

which ns ls -l $NS_HOME/bin/ns

如果 which ns 输出了完整路径,说明 PATH 生效;如果提示 command not found,再检查 bashrc 里有没有语法错误、路径目录名是不是写错。validate ns 能正常启动,执行 ns,会进入一个交互式 tcl shell,看到 %. 提示符就说明基本可用。

Windows 上不建议直接双击 ns.exe。NS2 的 Windows 移植版依赖模拟环境,直接在 cmd 里跑 ns 极易报缺失动态库。我这边带过的新人,最后都用 WSL 或虚拟机里的 Linux 完成全部仿真,稳定性好很多。

3. 用最小 NS2 代码跑通链路仿真:从 tcl 脚本到 out.tr

3.1 最小可运行的 tcl 仿真骨架

回到 ns2.rar 里的那一堆脚本,不管它多复杂,核心骨架都是同一套:创建 Simulator、定义 trace 输出、创建节点和链路、挂载流量、调度开始和结束时间。下面这段是我用来验证环境是否正常的最小示例代码,可以直接保存为 ns2_minimal.tcl 运行。

# ns2_minimal.tcl # 最小网络仿真:两个节点,一条链路,一个CBR流量源 set ns [new Simulator] set trace_fd [open out.tr w] $ns trace-all $trace_fd set nam_fd [open out.nam w] $ns namtrace-all $nam_fd proc finish {} { global ns trace_fd nam_fd $ns flush-trace close $trace_fd close $nam_fd exec nam out.nam & } set n0 [$ns node] set n1 [$ns node] $ns duplex-link $n0 $n1 1Mb 10ms DropTail set udp0 [new Agent/UDP] set null1 [new Agent/Null] $ns attach-agent $n0 $udp0 $ns attach-agent $n1 $null1 $ns connect $udp0 $null1 set cbr0 [new Application/Traffic/CBR] $cbr0 set packetSize_ 500 $cbr0 set rate_ 800k $cbr0 attach-agent $udp0 $ns at 0.1 "$cbr0 start" $ns at 4.5 "$cbr0 stop" $ns at 5.0 "finish" $ns run

逻辑说明:前两行创建 Simulator 实例并打开 out.tr 和 out.nam 两个输出文件。finish 过程在所有事件完成后被调用,负责冲刷缓冲区、关闭文件、启动 nam 可视化。接着创建两个节点,用 duplex-link 建立双向链路,带宽 1Mb、延迟 10ms、队列类型 DropTail。再挂载一个 UDP 发送代理和一个 Null 接收代理,中间用 CBR 流量填充链路。最后的 $ns at 语句把三个时间点和对应动作绑定到调度器上。

参数说明:packetSize_ 是 CBR 包大小,单位字节,这里 500 字节接近实际网络中的小包。rate_ 是发送速率,800k 表示 800kbps,链路带宽是 1Mb,这样的配比是为了让链路刚好处于不饱和到轻微拥塞之间,跑出来的 trace 才看得出排队和丢包现象。如果你把 rate_ 改成 100k,链路完全空闲,丢包率会变成 0,很多论文对比图就是这么“做”出来的,但不建议。

3.2 参数怎么调:节点、链路、流量模型和定时器

ns2.rar 包里的脚本不会老老实实只跑两个节点,你看到的更多是几十个节点随机分布、多条 TCP 流并发的场景。要改这些,首先得知道参数在哪个层级。

节点数量由 $ns node 出现的次数决定,通常配合循环语句创建。链路的核心参数有三个:带宽、延迟和队列管理算法。DropTail 是简单丢弃尾部,适合基础实验;RED 和 SFQ 适合拥塞控制研究,真实网络里的路由队列大多也不是简单的 DropTail,论文对比时要注意说明。队列长度的设置在链路对象上,默认值往往不够用,批量传输大文件时容易提前丢包,我会习惯显式设置 queue-limit。

流量模型方面,CBR 是恒定速率发送,适合考察丢包和延迟上限;TCP 流要配 FTP 或 Telnet 这类 Application 才会真正搬运数据。很多人从 ns2.rar 拿到的脚本里,TCP 和 CBR 混在一起,trace 文件事件非常密集,awk 统计时必须区分包类型,否则会把 TCP 的 ACK 包也算进 CBR 吞吐量里,得出来的数字直接失真。

定时器方面,$ns at 后的第一项是时间,第二项是字符串形式的命令。时间不一定非要用绝对时间,用变量做偏移更稳妥,比如 start_time 加 duration。我习惯把 CDN traffic 的开始时间放在 0.1s 而不是 0s,给协议栈一点建立邻居表的时间,无线场景尤其明显,0 时刻发包经常撞上没有建立路由导致的丢包。

3.3 运行与确认:ns 命令执行后该检查哪些输出

执行命令只需要一行,但执行完不能只看有没有报错,还要确认输出文件是否真的写入了数据。正确姿势是先运行:

cd ~/ns2lab ns ns2_minimal.tcl

运行过程中终端通常没有输出,但卡顿几秒后会自动调用 nam。如果终端静默退出,应立刻检查 out.tr 是否生成、文件大小是否大于零、nam 窗口是否出现。正常的 trace 文件至少有几百行,每一行的首字段是事件类型,第二字段是发生时间。

我验证脚本是否真正“跑起来”的标准,不是看 nam 窗口能不能弹出来,而是看 trace 文件里能不能同时找到入队、出队、接收、丢弃四类事件。如果只有入队和接收,链路太空闲;如果丢弃事件占到三成,说明链路配置过载。这两种情况都不适合直接拿去做论文图,先回头调整 rate 和 queue-limit 再继续。

一个反直觉的槽点:NS2 脚本里包名写错不会在运行时立刻崩溃。比如把 Agent/UDP 写成 Agent/Udp,NS2 可能到创建对象那一步才报 invalid command name,而整个文件前面几十行已经执行完了。所以改完脚本不要盯着终端等结果,直接看 trace 文件首尾时间戳有没有覆盖完整仿真时长,这比肉眼排查代码快得多。

4. 从 NS2 trace 提取论文指标:awk 统计吞吐量、丢包率与平均端到端延迟

4.1 读懂 trace 文件:一个数据包从入队到收到的完整轨迹

NS2 的 trace 文件不是给人密密麻麻读的,理解它的核心是搞清楚每一行的字段含义。以第 3 章的脚本为例,out.tr 里典型一行长这样:

+ 1.0021 0 1 cbr 500 ------- 0 0.0 1.0 0 12 r 1.0105 1 0 cbr 500 ------- 0 1.0 0.0 3 8

按空格切分后,$1 是事件类型,加号表示入队,减号表示出队,r 表示接收,d 表示丢弃。$2 是时间戳,单位秒。$3 是源节点 ID,$4 是目的节点 ID,$5 是包类型,$6 是包大小字节数。$9 和 $10 是源地址和目的地址的“节点.端口”形式,$12 是唯一包 ID。

一个数据包的完整轨迹通常是这样上演的:发送端应用产出数据后,先在源节点队列入口出现一条加号记录;调度器把包移出队列时出现减号记录;包到达目的节点后,出现一条 r 记录;队列满导致容不下时,出现 d 记录。注意 d 记录可能出现在入队阶段,也可能出现在中间转发节点,这取决于丢包发生在哪一跳。

对新手来说,做数据分析前先做一次“代码整理”非常关键。把每个实验的 tcl、tr、awk 脚本放进同一个目录,文件名带上日期和参数,比如 rtt10ms_rate800k.tr。不要把所有结果都堆进同名 out.tr,跑第二轮实验就把上一轮数据覆盖了,这种失误浪费的时间比写 awk 脚本多得多。

4.2 一手 awk 统计脚本:吞吐量、丢包率、平均端到端延迟

awk 是处理 NS2 trace 最顺手的工具,下面这段脚本可以直接保存为 stat.awk,统计 CBR 流的三个核心指标。它按传统格式解析,字段约定以 4.1 节为准,实际使用前先拿 head 命令看一眼你的 trace 格式,确认 $5 位置确实是 cbr。

#!/usr/bin/awk -f # stat.awk: 统计CBR业务吞吐量、丢包率与平均端到端延迟 BEGIN { sent = 0; recv = 0; recv_bytes = 0; delay_sum = 0; delay_cnt = 0; } { if ($5 == "cbr") { # 入队时记录包的发送时间,用唯一包ID做key if ($1 == "+") { sent++; send_time[$12] = $2; } # 接收时累加字节数并计算端到端延迟 if ($1 == "r") { recv++; recv_bytes += $6; delay_sum += $2 - send_time[$12]; delay_cnt++; if (start == 0) start = $2; end = $2; } } } END { if (sent > 0) { drop_rate = (sent - recv) / sent * 100; } else { drop_rate = 0; } duration = end - start; if (duration <= 0) duration = 1e-9; printf "发送CBR包数: %d\n", sent; printf "接收CBR包数: %d\n", recv; printf "丢包率: %.2f%%\n", drop_rate; throughput = recv_bytes * 8 / duration / 1e6; printf "吞吐量: %.3f Mbps\n", throughput; if (delay_cnt > 0) { printf "平均端到端延迟: %.6f ms\n", delay_sum / delay_cnt * 1000; } else { printf "平均端到端延迟: 无接收包\n"; } }

逻辑说明:第一部分在入队事件里记录发送时间,以 $12 这个唯一包 ID 作为关联键;第二部分在接收事件里累加字节数和延迟。END 块里计算丢包率,分子是发送数减接收数,分母是发送数;吞吐量用接收字节数乘 8 换成比特,再除以仿真持续时间;延迟是接收时刻减发送时刻,累加后取平均。

参数说明:整段代码只统计包类型为 cbr 的行,这是刻意做的筛选,避免 TCP ACK 和 UDP 的其他业务混进来。如果你的 trace 格式里包类型出现在别的字段,先修改 $5 为对应字段号。delay_sum 的单位是 trace 里的秒,最后乘 1000 转成毫秒输出,论文里更常用毫秒。start 这个变量记录第一个接收包的时间,end 是最后一个接收包的时间,duration 取的是这个差值而不是总仿真时长,这样能剔除尾部无包时间段的影响。

运行它的命令:

awk -f stat.awk out.tr

运行结果类似:

发送CBR包数: 17885 接收CBR包数: 17603 丢包率: 1.58% 吞吐量: 0.793 Mbps 平均端到端延迟: 8.213 ms

这里吞吐量 0.793Mbps 略低于配置的 800kbps,是因为丢掉了 1.58% 的包,两者互相印证。如果出现吞吐量远高于链路带宽,或者延迟比链路延迟还小,第一反应应该是 awk 字段取错了,不是算法有问题。

4.3 把统计结果整理成可出图的数据

awk 输出了三个数字,但论文里的曲线图要有多个时间点或者多组参数。常见做法是写一个外层shell循环,把不同 rate 或跳数下的 trace 跑一遍,再合并成可出图的数据文件。下面是一个简单的数据整理思路:

for rate in 100k 300k 500k 700k 900k; do sed "s/800k/$rate/" ns2_minimal.tcl > tmp_$rate.tcl ns tmp_$rate.tcl awk '/cbr/ {if ($1=="r") recv += $6} END {print "'"$rate"'", recv*8/4.9/1e6}' out.tr >> throughput.dat done

逻辑说明:每一轮循环用 sed 把 tcl 模板里的发包速率替换成当前值,生成临时脚本并运行,然后从同名 out.tr 里提取接收字节数,最后把速率和计算出来的吞吐量追加到 throughput.dat。这样得到的 data 文件每一行是一个速率一吞吐量点,可以直接交给 gnuplot 画折线图。

参数说明:sed 替换的 800k 要和 tcl 文件里实际写的字符串完全一致,否则替换不生效。awk 命令里的 4.9 是仿真持续时间,大约是最后一个包接收时间到第一个包接收时间的差值,精确值以 trace 实际为准,写死数字前先 head 几个 trace 行确认时间范围。每个实验跑完一定要把 out.tr 改名归档,下一个循环会覆盖它。

如果你嫌 shell 循环麻烦,也可以在 awk 里直接按 $2 的时间段分桶累加,输出的是时间窗粒度上的吞吐量变化,这适合观察拥塞发生时刻的网络行为。两类方法没有优劣,论文需要横向对比参数就用前者,需要展示单个实验的时间演化就用后者。

5. NS2 代码避坑现场:五个让我翻过车的常见问题

5.1 现象:终端提示 ns 找不到,或 cmd 提示“不是内部或外部命令”

原因:没有执行环境变量设置,或者用户用的是 Windows 自带的 cmd 试图直接跑 ns 命令。NS2 安装后的二进制文件在指定子目录里,OS 默认不会自动扫描整个磁盘找它。

解决:按 2.3 节的 bash 配置把环境变量写进 ~/.bashrc,执行 source 后重新打开终端。Windows 用户不要在 cmd 里硬试,改用 WSL 或虚拟机里的 Linux 跑。验证方式用 which ns,看到完整路径就算通过。

5.2 现象:脚本运行不到一秒就退出,out.tr 是空文件

原因:tcl 脚本里 $ns run 没有执行到,或者前面某个对象创建失败但错误信息被掩盖。常见情况是脚本里调用了 namtrace-all 但 nam 文件路径错误,或 finish 过程在 run 之前被意外触发。

解决:先手动删掉输出文件,然后 ns 脚本名 运行,观察终端有没有输出 error。很多 NS2 脚本错误只有一行提示,比如 invalid command name,顺着提示行号往上看就能找到位置。out.tr 是空的,优先检查 trace-all 和 namtrace-all 两个命令的文件路径是否可写,目录不存在时 NS2 会静默失败。

5.3 现象:nam 窗口打开但界面全黑,节点一个都看不见

原因:nam 文件里没有节点坐标信息。NS2 节点创建后不会自动获得坐标,需要手动用 $n set X_ $n set Y_ $n set Z_ 赋值。从复杂脚本中抽出的 nam 文件常因为坐标语句被删而出现全黑窗口。

解决:在创建节点的循环里补上坐标设置,比如 $node set X_ [expr random()*500],$node set Y_ [expr random()*500]。数值范围要与 nam 视图大小匹配,否则节点跑到视野外依然显示黑屏。展位图上坐标单位是米,无线仿真距离动辄几百米,按这个量级设置即可。

5.4 现象:awk 统计出来的吞吐量小数完全不像真值

原因:字段位置取错,或统计时混入了非目标业务包。NS2 不同版本 trace 格式有细微差异,2.31 和 2.35 的字段偏移不完全一样;另外 TCP 应用产生的 ACK 包大小只有几十字节,如果按包计数统计吞吐量,会得到一个不符合想象的小数字。

解决:先执行 head -n 5 out.tr 人工检查字段结构,确认包类型在第几列、包大小在第几列。统计时加上 $5 == "cbr" 的筛选条件,只对目标业务计数。吞吐量用字节数乘 8 再除以时间,带宽单位是 bps,不是 pps,不要拿包数直接乘包大小。

5.5 现象:网上抄来的脚本在 NS2.35 上报 invalid command name

原因:脚本版本或依赖类不匹配。ns2.rar 包里很多脚本来自 NS2.28 或 2.31 时代,使用的类名、变量名和默认参数在 2.35 里已经不同;还有一种情况是从多个实验脚本里零散拼凑,缺了某个必要的 Agent 或 Queue 类定义。

解决:先定位报错行,看是哪个对象创建失败。例如报 invalid command name Application/Traffic/EXPOO,说明当前安装里没有 EXPOO 流量模型,换成 CBR 或检查是否漏了加载对应模块。实在找不到替代,就把这条报错信息连同类名一起搜索,看它在老版本里对应什么实现,再做等价替换。大型脚本建议用 git 或普通 diff 记录改动,正本文件备份一份,改坏了随时回退,这是做 NS2 实验最省心的后悔药。

6. 改 NS2 核心代码做自己的协议:C++ 源码、OTcl 注册与重新编译

6.1 先定位:协议代码散落在源码树哪些目录

如果你拿到的 ns2.rar 里带了完整源码,或者你已经解压了 ns-allinone-2.35,那么改代码前先熟悉目录结构。NS2 的核心代码分类相当规整:tcp/ 目录放 TCP 相关实现,mac/ 目录放 802.11 和 CSMA 等 MAC 层协议,routing/ 目录放 AODV、DSR、DSDV 等路由协议,queue/ 目录放队列调度算法,common/ 目录放 packet、agent 等基础类。

我改的最多的是 routing/ 和 mac/。路由协议的邻居表维护、路由发现、路由维护逻辑都在对应目录的 .h 和 .cc 文件里;MAC 层协议则关注信道状态和帧间间隔。定位时先搜类名,比如 AODV 对应的文件名通常就叫 aodv.cc,打开文件头部注释就能看到协议实现所属版本和作者信息。

在这一步不要动 packet.h 里包头格式的代码,除非你真的知道自己要加什么字段。NS2 的包头结构通过 offset 机制管理,加一个字段要同步修改包追踪和序列化代码,牵一发动全身。对大多数实验来说,改 Agent 的发送接收逻辑就够了。

6.2 添加一个新 Agent 的最小改动:C++ 类、TclClass 注册、Makefile

下面是一份最简自定义 Agent 的示例代码,目标是在 tcl 脚本里能通过 new Agent/Example 创建它。它不改任何现有包头,只在 recv 函数里把收到的包直接释放,相当于一个能接入拓扑但内容为空的代理。

// example_agent.cc #include <stdio.h> #include "agent.h" #include "packet.h" class ExampleAgent : public Agent { public: ExampleAgent() : Agent(PT_UDP) {} virtual void recv(Packet *p, Handler *h) { // 收到包后直接释放,不转发也不统计 Packet::free(p); } }; static class ExampleAgentClass : public TclClass { public: ExampleAgentClass() : TclClass("Agent/Example") {} TclObject* create(int, const char*const*) { return (new ExampleAgent); } } class_exampleagent;

逻辑说明:ExampleAgent 继承自 Agent 基类,构造函数传入 PT_UDP 表示这是一个 UDP 类型的代理。recv 是收到包时的回调,这里只调用 Packet::free 释放内存,不产生任何后续行为。底部的 ExampleAgentClass 是 NS2 的 OTcl 绑定,TclClass 构造参数里的字符串 Agent/Example 决定了脚本里如何实例化它。

参数说明:类名和 tcl 名称可以不同,但建议保持一致,省去人在代码和脚本两头猜名字的麻烦。包类型参数 PT_UDP 要让 Agent 的构造函数获得正确的包类型映射,否则 trace 文件里的包类型标识会显示异常。

接下来要把这个文件加进编译体系。第一步把 example_agent.cc 放到 ns-2.35 源码目录下;第二步编辑 Makefile.in,找到 OBJ_CC 变量,在合适位置追加 example_agent.o;第三步运行 ./configure 和 make。make 时间取决于机器性能,几分钟到十几分钟都正常,编译完成后再执行:

cd ~/ns-allinone-2.35/ns-2.35 ./configure make

配置和编译完成后,写一个 tcl 冒烟测试脚本验证新 Agent 能创建并与现有节点接线。我用下面这段验证,它能跑通说明注册无误。

set ns [new Simulator] set n0 [$ns node] set n1 [$ns node] $ns duplex-link $n0 $n1 1Mb 10ms DropTail set a0 [new Agent/Example] set a1 [new Agent/Null] $ns attach-agent $n0 $a0 $ns attach-agent $n1 $a1 $ns connect $a0 $a1 $ns at 0.5 "exit" $ns run

验证时输出没有 invalid command name 即通过,不用纠结这条测试本身有没有产生统计效果,它的价值只在于确认 Agent/Example 已经被系统识别。如果真的出现错误,优先检查 make 日志里 example_agent.cc 有没有被编译进去,Makefile.in 的 OBJ_CC 遗漏是最常见的情况。

6.3 重新 make 并验证改动生效

改完 C++ 代码后,最怕的不是编译报错,而是 tcl 脚本还在用旧版本的 Agent 类名。确认改动生效的唯一硬标准,是 make 完成后的那个 ns 二进制能运行 new Agent/Example 对应脚本。如果修改的是已有协议,比如把 aodv.cc 里的 Hello 周期从 1 秒改成 2 秒,验证方式更直接:跑同样的场景,看 trace 文件里 Hello 包出现的时间间隔是否变成 2 秒。

我习惯在 make 之前先把旧二进制做个备份,cp ns ns_bak,这样新代码跑出诡异结果时能一键回退到旧版本,定位是代码问题还是场景问题。make 失败时不要反复重跑,先看第一处报错文件名和行号,NS2 源码在旧编译器上的报错经常是语法老式写法引起,需要加头文件或改类型转换。

改代码这种事,真正稳的验证是在完整跑业务场景后对拍 trace。比如你只改了一个队列长度,改动前后接收端的吞吐量变了一点,延迟却跳了一大截,这就要怀疑是不是代码里连带改了别的参数。做 NS2 实验这几年,我最大的收获就是只改一个参数跑一组实验,改完先对比基准场景再改下一个。宁可多花几轮 script 执行时间,也别让两个改动混在一起,否则最后没有办法向自己解释结果,更没有办法向审稿人解释。

希望这篇笔记能帮你把 ns2.rar 里的这些老代码真正跑起来,也帮你少踩几个我当年踩过的坑。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询