多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

NS2网络仿真从入门到实战:架构解析、脚本编写与避坑指南

NS2网络仿真从入门到实战:架构解析、脚本编写与避坑指南 简介面向NS2初学者的代码学习包涵盖网络仿真中TCP/IP协议模拟、路由协议实现、流量控制与拥塞控制、移动性模型、性能统计、OOPSI扩展接口等关键知识点。压缩包共28个文件以tcl脚本为主辅以h/cc源码、awk统计脚本、nam/tr仿真输出及场景配置文件整体仅623KB便于快速下载运行。已有222人学习使用。通过研读和运行这些示例可掌握NS2的Tcl配置方法、网络拓扑定义、参数设置与结果分析理解事件驱动模拟机制并借助OOPSI自定义协议节点还能通过gnuplot和xgraph生成图形化结果直观观察网络行为。适合作为高校网络课程实验或自学NS2的入门参考也可用于快速复现经典仿真场景。1. 老牌网络模拟器 NS2 的代码包跑课设和论文仿真够不够用NS2 在很多人印象里已经过时了但打开知网和高校课程设计库它仍然是网络仿真方向出现频率最高的工具之一。原因不复杂NS2 用 C 实现协议、用 Tcl 脚本编排仿真场景改路由协议跑一轮对比实验链路比 ns-3 和 OMNeT 短得多。这份 ns2.rar 把仿真里最常用到的几块代码收在了一起第二章的 trace 与 nam 可视化、第三章的 Tcl 类与继承、第六章的 gnuplot/xgraph/gawk 出图工具、第七章的 mflood 路由源码。把这四块串起来正好是一篇论文仿真章节从搭场景到出曲线的完整链路。适合手里没有现成脚本、想快速复现网络仿真的学生和从业者也适合刚开题、需要先跑通一个基线实验的人。2. 第一次跑通 NS2 脚本从节点拓扑到 trace 文件的一整条链路拿到代码包第一步不是逐行读源码而是先把一个最小场景跑起来确认环境没问题。NS2 的构成决定了你必须同时习惯两种语言底层协议是 C 写的编译成可执行文件仿真场景是 Tcl 写的由ns解释执行。很多人在这一步就开始翻车因为 Tcl 脚本里的报错信息几乎不会告诉你哪里写错了只会告诉你哪个命令不认识。2.1 NS2 的双层架构为什么改一个协议要动 C 和 Tcl 两层NS2 的事件驱动模型可以简化成一句话模拟器维护一个事件队列按时间顺序取出事件并执行。数据包发送、接收、队列排队、定时器超时全部是事件。Tcl 脚本负责把这些事件排进去C 对象负责事件触发时的具体行为。当你修改一个协议时改动点通常横跨两层。路由表的维护和转发逻辑写在 C 里配置这个协议的参数和挂载位置写在 Tcl 里。这就是 NS2 和后来 ns-3 最大的差异ns-3 几乎可以全部用 C 完成NS2 却必须理解 Tcl 和 C 的映射关系。比如你在 Tcl 里写new Agent/TCP它实际是在实例化一个编译好的TcpAgent类你在 Tcl 里给某个属性赋值底层对应的是 C 类里的成员变量。理解这个结构之后你再看代码包里的文件就不慌了.tcl文件是你能直接改的.cc/.h文件是需要重新编译才能生效的.tr是仿真跑完生成的轨迹文件.nam是给 nam 动画工具看的可视化文件。分清这四类文件后面所有操作才有抓手。注意拿到任何 NS2 代码包第一件事是确认它配套的 NS2 版本。2.34 和 2.35 的 Tcl 兼容性大体一致但和老版本 2.28 以下的差异很大。包里脚本如果命名的 Agent 类型在你当前版本里不存在会直接报错。2.2 跑通 example1.tcl最小二节点仿真与 trace 字段对照代码包里第二章的 example1 系列是标准的入门组合一个example1.tcl脚本跑完生成example1.tr和example1.nam。这个场景通常是两个或三个节点之间建立 TCP 连接然后传输 FTP 流量。我们先看一个结构上等价的脚本理解每一行的作用。# example1.tcl 的最小等价结构用于理解 NS2 脚本骨架 set ns [new Simulator] ;# 创建模拟器实例所有事件队列都由它管理 proc finish {} { ;# 仿真结束回调负责收尾 global ns tracefile $ns flush-trace ;# 把缓冲区的 trace 写入文件 close $tracefile exit 0 } set tracefile [open example1.tr w] ;# 打开 trace 写入句柄 $ns trace-all $tracefile ;# 开启全量轨迹追踪 set n0 [$ns node] ;# 创建节点 0 set n1 [$ns node] ;# 创建节点 1 $ns duplex-link $n0 $n1 1Mb 10ms DropTail ;# 双向链路带宽 1Mb延迟 10ms丢尾队列 set tcp [new Agent/TCP] ;# 创建 TCP 发送端代理 $tcp set packetSize_ 1000 $ns attach-agent $n0 $tcp ;# 把 TCP 代理挂到节点 0 上 set sink [new Agent/TCPSink] ;# 创建 TCP 接收端代理 $ns attach-agent $n1 $sink ;# 把接收端挂到节点 1 $ns connect $tcp $sink ;# 建立连接 set ftp [new Application/FTP] ;# FTP 应用模拟大文件传输 $ftp attach-agent $tcp $ns at 0.1 $ftp start ;# 仿真第 0.1 秒开始发数据 $ns at 4.5 $ftp stop ;# 第 4.5 秒停止 $ns at 5.0 finish ;# 第 5 秒调用收尾过程 $ns run ;# 启动模拟这段脚本的关键点都在参数上1Mb是链路带宽10ms是传播延迟DropTail是队列管理算法。NS2 的链路带宽单位是 bit/s所以1Mb指 1Mbps不是 1MB/s如果在这里写成1MB你后面统计吞吐量时会发现数值全是 0因为 NS2 不认这个单位。跑这个脚本的方式很简单ns example1.tcl跑完后同目录下会生成example1.tr和example1.nam。example1.tr是核心产物后面第 4 章的所有统计都从它来。每一行代表一个事件格式是固定的字段位置内容示例值$1事件类型r 接收 入队- 出队d 丢弃$2仿真时间0.50176$3源节点编号0$4目的节点编号1$5包类型tcp 或 cbr$6包大小1000$7标志位-------$8流标识 fid0$9源地址.端口0.0$10目的地址.端口1.0$11包序号0$12包唯一 ID1每次统计都要先想清楚你关心哪几列而不是盲目grep。比如你要算接收端的包数就应该过滤$1 r且$4 1如果要算丢包就过滤$1 d。第 4 章会给出具体 gawk 写法。3. 拆包看代码第二章多节点拓扑、第三章 Tcl 类与第七章 mflood 路由源码跑通最小例子之后就可以看代码包里更复杂的示例了。这一章我把包的目录结构和每部分对应解决的问题拆开讲。压缩包里的文件归到三块第二章是 example1/example2 两个场景第三章是 Tcl 类定义示例第七章是 mflood 洪泛路由的实现。3.1 从 example2.tcl 学多节点拓扑把总线型改成哑铃型example2 相比 example1 最大的变化是节点变多流量变多。它通常包含多个 TCP 连接和不同方向的流量trace 文件里fid字段不再是 0而是区分不同流。example2.tr里你会看到同一条 TCP 流的包用同一个fid这是后续画吞吐量对比图的分组依据之一。多节点拓扑的关键在duplex-link和node两个命令的配合。比如你要搭一个三条链路组成的拓扑常见做法是set n0 [$ns node] set n1 [$ns node] set n2 [$ns node] $ns duplex-link $n0 $n1 2Mb 10ms DropTail $ns duplex-link $n1 $n2 2Mb 10ms DropTail $ns duplex-link $n0 $n2 1Mb 20ms DropTail三条链路带宽和延迟不一样是为了模拟不对称的网络环境。NS2 里没有自动路由的概念节点之间默认走最短路径但这个最短路径是基于链路跳数的。如果你想让数据走特定路径需要手动设置路由或者用$ns rtproto指定路由协议。提示example2 这类多节点脚本里常见的坑是节点数量多但链路数不够导致某些流量实际不可达。排查时先数一下每个节点连了几条 duplex-link再对照 trace 里有没有出现d事件。读 example2 的代码时建议带着这三个问题去看一是每个 TCP 连接的发送端和接收端分别挂在哪个节点二是每条流的启动时间为什么错开三是 trace 文件里不同流的 fid 分布是否符合预期。3.2 common_example.tcl 和 class_example.tclTcl 里的类、继承与回调第三章的两个文件属于给 Tcl 脚本做结构化的复杂度。NS2 的 Tcl 不是纯脚本它自带一套面向对象扩展——OTcl。common_example.tcl通常演示的是如何把重复的建链、挂代理操作封装成可复用的过程class_example.tcl则演示如何用Class和instproc定义自己的节点或代理类型。典型的类定义长这样# 定义一个自己的节点类继承 NS2 的 Node Class MyNode -superclass Node MyNode instproc init {args} { $self next {color blue} ;# 先调用父类构造函数设置节点颜色 $self label custom-node ;# 设置节点标签 } # 使用这个自定义节点类创建节点 set n0 [new MyNode]这段代码的核心在instproc init它重写了节点的构造函数。在 OTcl 里init是每个对象创建时都会调用的方法$self next是调用父类的同名方法必须先执行否则后续的label、color操作可能会因为没有初始化完整而报错。封装过程的模式也很实用。比如你总是要创建一条 1Mb 带宽的链路可以写成proc make-link {ns n1 n2 bw delay} { $ns duplex-link $n1 $n2 $bw $delay DropTail }然后每次调用make-link $ns $n0 $n1 1Mb 10ms就行。这样做的价值是当你要跑多组不同参数的实验时只需要在调用处改参数不需要复制粘贴整段链路配置减少低级错误。3.3 mflood 路由源码一个最简洪泛协议的实现骨架第七章的 mflood 是包里最有含金量的一部分。它实现了一个洪泛式路由协议逻辑很直接节点收到数据包后除了来源节点之外向所有邻居重新广播如果收到重复包直接丢弃。你拿它来理解如何在 NS2 里添加一个新协议是最合适的样本因为洪泛逻辑不像 DSDV 或 AODV 那样要维护庞大的路由表核心只有两件事去重和广播。NS2 里一个路由协议的完整代码一般包含这几个部分文件或组件职责mflood.h类定义成员变量和函数声明mflood.cc协议核心逻辑包括 recv、send、定时器回调包头定义自定义的包格式用于携带洪泛序号Tcl 绑定通过TclClass把 C 类注册成 Tcl 可调用的 Agent 类型核心逻辑的骨架通常长这样// mflood.cc 中 recv 函数的核心逻辑骨架 void MFloodAgent::recv(Packet* p, Handler* h) { hdr_ip* ih hdr_ip::access(p); // 情况一包的目的地址是自己或者是广播地址 if (ih-daddr() my_addr_ || (int)ih-daddr() IP_BROADCAST) { // 说明这个包已经到达目的地交给上层协议 uptarget_-recv(p, (Handler*)0); return; } // 情况二需要继续洪泛转发 // 真实代码里会检查包里的洪泛序号判断是否收过这个包 // 如果收过就直接 drop没收到才执行 send 广播 send(p, (Handler*)0); }这个例子的两个关键判断是daddr() my_addr_和洪泛序号去重。前者决定包是否到站后者决定是否重复转发。你如果想把洪泛改成只向部分邻居转发的变体就在send之前增加一层逻辑哪些邻居允许转发哪些不转发。这就是你第一篇论文里能做文章的地方。注意修改这些 C 文件之后必须在 NS2 源码根目录重新执行make再重新运行 Tcl 脚本改动才会生效。只改.cc文件不重新编译是新手最常见的问题后面第 5 章还会详细说。4. 把 trace 变成论文图表gawk 聚合与 gnuplot/xgraph 参数详解仿真跑完只是第一步论文需要的是曲线和表格。代码包第六章集中给了 gnuplot 和 xgraph 相关脚本还带 gawk 用于数据提取。这一章把从 trace 到图表这条链路拆成两步先用 gawk 把原始 trace 聚合成统计量再用 gnuplot 或 xgraph 出图。4.1 gawk 从 trace 里提数据吞吐量、丢包率、时延三个口径example1.tr里的原始数据是无法直接画图的因为每一行只是一个事件记录。你需要的指标是每秒钟收到了多少字节一共丢了多少个包包从发到收用了多久。这些都要用 gawk 按列聚合。先看最基础的吞吐量统计# throughput.awk统计目的节点每秒收到的 TCP 字节数 BEGIN { start_time 0 total_bytes 0 } $1 r $5 tcp { if (start_time 0) start_time $2 total_bytes $6 } END { duration $2 - start_time printf(simulation duration: %.2f s\n, duration) printf(total bytes: %d\n, total_bytes) printf(average throughput: %.2f Mbps\n, total_bytes * 8 / duration / 1000000) }执行方式gawk -f throughput.awk example1.tr这段脚本过滤条件是$1 r且$5 tcp意思是只统计接收事件里的 TCP 包。$6是包大小累加起来是总字节数。最后除以仿真时长再乘 8把字节换算成比特得到平均吞吐量。丢包率统计是另一个常见需求# loss.awk统计丢包率 BEGIN { sent 0 lost 0 } $1 { sent } ;# 入队事件代表数据包进入发送队列 $1 d { lost } ;# 丢弃事件代表包被丢弃 END { if (sent 0) printf(packet loss ratio: %.2f%%\n, lost * 100.0 / sent) }这里用事件作为发送总数有一定误差因为入队不代表最终发出去。更严格的统计是看-事件加d事件或者用 TCP 代理自身的统计变量。但对于课设和一般论文的对比实验/d口径已经足够说明问题前提是你每次实验都用同一套口径不要这个实验用下个实验用-那对比就失真了。时延统计稍微复杂需要按包的序列号关联发送和接收时间# delay.awk按包 ID 计算端到端时延 { if ($1 ) { send_time[$11] $2 } if ($1 r $11 in send_time) { total_delay $2 - send_time[$11] count } } END { if (count 0) printf(average end-to-end delay: %.4f s\n, total_delay / count) }$11是包序号$2是时间戳。入队时记下时间接收时算差值。这个口径没有考虑重传和乱序网络拥塞严重时误差会变大但用作相对比较没问题。4.2 gnuplot 出图与 xgraph 快速预览参数对照和中文乱码处理数据文件准备好之后下一步是画图。gnuplot 比 xgraph 更灵活能输出 eps 和 png论文投稿时普遍用 eps 格式。一个最简的 gnuplot 脚本如下# throughput.gnuplot set terminal png size 800 600 set output throughput.png set xlabel Time (s) set ylabel Throughput (Mbps) set grid plot throughput.dat using 1:2 with lines title TCP throughput运行方式gnuplot throughput.gnuplot这里using 1:2表示第一列是 x第二列是 y。with lines是折线如果数据点稀疏可以改成with linespoints。set grid是给图加网格评审老师普遍喜欢有网格的图看起来更好定位峰值。xgraph 的优势是交互式查看适合快速确认数据有没有问题比如有没有明显跳变、曲线是不是长得很怪xgraph -t Throughput Compare -x Time(s) -y Throughput(Mbps) throughput0.dat throughput1.datxgraph 的-t是标题-x和-y是坐标轴标注后面跟多个数据文件会画在同一张图里方便对比。提示gnuplot 输出 png 时如果标题或坐标轴中文显示成方块是因为终端不支持中文解决办法是全部用英文标注或者配置set terminal png时指定完整的 UTF-8 字体路径。我一般直接画英文标注入论文省得在字体配置上耗时间。5. NS2 翻车避坑手册五个高频问题的现象、原因与解决NS2 的问题有很强的共通性十个初学者有八个栽在同样的坑里。这一章我把最容易遇到的五个问题按现象 → 原因 → 解决列出来全部来自我和身边人跑 NS2 时的血泪排查记录。5.1 现象ns: command not found或者ns: No such file or directory原因ns可执行文件不在 PATH 环境变量里。NS2 编译完成后可执行文件在ns-allinone-2.35/ns-2.35/ns如果安装完没有执行环境变量配置Shell 就找不到它。解决先确认编译产物存在ls /path/to/ns-allinone-2.35/ns-2.35/ns确认存在后把路径加进~/.bashrcexport PATH$PATH:/path/to/ns-allinone-2.35/bin:/path/to/ns-allinone-2.35/tcl8.5.10/unix:/path/to/ns-allinone-2.35/tk8.5.10/unix export LD_LIBRARY_PATH$LD_LIBRARY_PATH:/path/to/ns-allinone-2.35/lib然后source ~/.bashrc。之后我用任何 NS2 脚本之前都会先跑一句which ns和ns -v确认当前环境里指向的是不是我预期的那一套避免装了多个 NS2 版本互相干扰。5.2 现象运行 Tcl 脚本报invalid command name Agent/TCP原因这个报错几乎都和安装时的 Tcl/Tk 库版本有关。NS2 2.35 依赖较旧的 Tcl 8.5如果你的系统装的是新版本 Tcl 8.6OTcl 类和 C 对象的绑定可能加载不完整Agent/TCP这个命令就没被注册。解决使用ns-allinone-2.35自带的 tcl/tk 目录不要用系统的版本。安装完成后检查echo puts $tcl_version; exit | tclsh如果是 8.6就需要调整LD_LIBRARY_PATH确保 NS2 优先使用自带目录下的libtcl8.5.so。另一个可能原因是运行脚本时用错了解释器——必须是ns不是tclsh。5.3 现象脚本正常跑完但example1.tr是空文件nam 窗口也不显示节点原因最常见的是trace-all和namtrace-all的顺序问题。nam需要单独开文件并写入namtrace数据如果你只执行了$ns trace-all没有执行$ns namtrace-all $namfile虽然不会报错但nam打开会黑屏。解决在脚本里补充 nam 追踪set namfile [open example1.nam w] $ns namtrace-all $namfile还有一个隐蔽原因仿真脚本在写入 trace 之前就调用了exit缓冲区没有刷新。正确姿势是像第 2 章那样用finish过程先$ns flush-trace再close最后exit 0。从那以后我所有脚本的第一步都是把 finish 过程写好再写主体逻辑。注意如果你改过脚本里的节点数量记得把 trace 文件名也换掉或者先删掉旧的.tr。NS2 默认覆盖写入但如果脚本中途崩溃旧文件可能残留半截内容你分析时会把两次实验的数据混在一起。5.4 现象gawk 统计出来的吞吐量比预期高出一个数量级原因单位问题。NS2 里链路带宽写的是1Mb是 1 Mbps每秒 125000 字节。如果你统计时直接把$6累加再乘 8 除以时间得到的单位是 bit/s显示出来就会是 1000000 这个数量级如果你在脚本里把带宽写成了1MBNS2 不会报错但实际速率会异常放大。解决统一成 Mbps 输出公式是total_bytes * 8 / duration / 1e6。另外检查 trace 里是否混入了非数据包比如 ACK 包。TCP 场景下/tcp事件里既有数据包也有 ACK如果只关心应用层吞吐过滤条件要从$5 tcp细化到具体的应用层代理类型或者直接使用 FTP/CBR 应用自身的统计变量。5.5 现象改了 mflood.cc 源码重新make之后运行结果没变化原因我见过三个层次的原因。第一层是没进对目录就开始 make在ns-2.35目录外执行make根本没编译到这个文件第二层是 make 认为文件没变化因为系统时钟错乱导致时间戳比目标文件早第三层是 make 编译成功但执行时 Tcl 脚本里用的还是旧的 Agent 名称新类没有注册进去。解决按顺序排查。先确认当前目录cd /path/to/ns-2.35然后强制重编touch mflood.cc mflood.h make如果还不行直接清理后全量编译make clean make最后验证新代码是否生效最直接的办法是在 mflood.cc 里加一句调试输出到 stderrprintf(mflood recv called, addr%d\n, my_addr_);重新编译运行后如果控制台没有任何输出说明你的 Tcl 脚本用的可能根本不是这个协议实现而是 NS2 自带的另一个同类型协议。先确认Agent/mflood这个类名是否正确注册到了 Tcl 里。6. 把示例改成自己的实验mflood 参数调整与结果对照跑通代码包、解决掉环境问题之后你已经具备把这份资源变成自己的实验的能力。关键在于两点改参数以及验证改动没有把协议改崩。6.1 改一个参数看整条链路变化第一个实验不要去改协议逻辑先改场景参数。比如把链路带宽从1Mb改成5Mb或者把队列从DropTail换成RED再跑一遍 gawk 统计对比两条吞吐量曲线。这个过程的目的是建立对 NS2 的直觉哪些参数影响曲线的形状哪些参数只影响数值大小。通常你会在这一步发现DropTail在高负载下丢包率会陡然上升而RED的曲线更平滑。6.2 改造 mflood 后怎么验证没改崩修改 mflood 之后把仿真时间拉长到 50 秒节点数扩展到 10 个以上观察三个检查点第一r事件是否持续存在如果中途长时间没有接收事件说明洪泛陷入死循环或包被全部丢弃第二d事件的比例是否失控洪泛协议天然有大量重复包但如果丢包率超过 50%大概率是去重逻辑出了问题第三运行结束后 trace 文件里是否存在大量s发送但没有对应接收记录的包。我自己每次改完协议都会保留改之前的 trace 文件用 gawk 分别统计新旧版本的数据再放到 gnuplot 里画成对比图。这个习惯帮我抓出过不止一次看似正常、实际节点已经失联的 bug。希望这份拆解和踩坑记录能帮你在 NS2 上少折腾几个通宵顺利把仿真章节做扎实。本文还有配套的精品资源点击获取
返回列表