☰
AODV自组网仿真从零到可复现:ns-2参数与性能统计全攻略
2026/10/1 3:31:50 网站建设 项目流程

简介:面向移动自组网仿真的AODV路由协议头文件资源包,适合网络协议学习者、无线自组网研究人员及准备开展NS2/NS3等环境仿真的开发者。AODV是一种按需路由协议,通过RREQ、RREP、RERR三类消息完成动态路由发现与维护;资源精选3个头文件,分别对应协议核心结构、报文封装逻辑与路由表管理,rar包仅8KB,轻量易读,便于直接嵌入实验工程或作为协议模块的参考实现。目前已有243人学习下载。借助该资源可快速掌握AODV的数据结构设计与接口划分,理解移动节点间路由建立、失效告警及转发表维护的实现要点,为后续仿真参数调优和路由性能分析提供代码级支撑,也适合作为课程设计或科研入门的基础材料。

1. 这个自组网仿真的题,最后都比在 AODV 上反复调参

课程设计里“移动自组网仿真”这类项目,十有八九最后都会落到同一个点上:把 AODV 协议放进仿真器里跑出可靠的路由数据。aodv.rar 这个包名在高校里流传很广,里面通常就是 AODV 的源码、TCL 仿真脚本和几份统计工具,但多数人解压后第一步就卡住——ns-2 装不上、脚本报错、数据对不上。这篇笔记想解决的,就是把这个过程从“玄学”变成“可复现”:AODV 协议在自组网仿真里究竟怎么工作、怎么搭出第一个能出数据的场景、怎么把 trace 文件算成 PDR 和时延,以及那些让人反复翻车的参数坑。适合正在做移动自组网课程设计、毕业设计,或者想用仿真验证路由算法的人。

2. 自组网仿真为什么绕不开 AODV:协议机制与选型参数

2.1 AODV 报文交互:RREQ→RREP→RERR 与序列号

AODV 的全称是 Ad hoc On-Demand Distance Vector Routing,中文常译作按需平面距离矢量路由协议。它最鲜明的特点就是“按需”:不是像传统距离矢量协议那样周期性全网广播路由表,而是当源节点真正要发包且没有可用路由时,才发起一次路由发现过程。这个“按需”设计,让它在节点频繁移动、拓扑剧烈变化的自组网环境里,比周期性交换路由表的 DSDV 更省控制开销。

路由发现过程分三步。源节点先广播一条 RREQ(Route Request)报文,这条报文会被中间节点继续转发,直到到达目标节点,或者到达一个缓存里有到达目标节点有效路由的中间节点。目标节点收到 RREQ 后,沿反向路径单播回复一条 RREP(Route Reply)。源节点收到 RREP,就把这条路径写入路由表,开始发送数据。如果链路因为节点移动而断开,断链处的上游节点会发起 RERR(Route Error)通知相关节点,把失效路由标记为不可用。

AODV 防环的核心机制是目标序列号(Destination Sequence Number)。每个目标节点会维护一个单调递增的序列号;中间节点只有在“序列号更新、跳数更短”时才会更新路由。仿真里最容易观察到的一个现象是:序列号处理不对时,RREP 会在两个节点之间来回震荡,数据包进入一个死循环,trace 里能看到同一个 IP 包被反复转发。这也是为什么你在 ns-2 里打开 aodv.cc 会看到大量对 seqno 的 if 判断。

此外,AODV 依靠 HELLO 报文维护邻居关系。节点每隔一段时间广播一次 HELLO,如果在链路超时时间内没有收到邻居的 HELLO,就判定链路断了,触发 RERR。ns-2 的 AODV 实现里,这个动作是本地链路检测(local repair)和邻居管理的基础。仿真里如果 HELLO 间隔和链路超时参数配得不合理,会出现“路由还在但邻居早走了”的假象。

2.2 三种经典协议怎么选:AODV 在仿真里为什么最稳

做自组网仿真,绕不开 DSDV、DSR、AODV 这三个经典路由协议。ds-2 源码里三个协议都自带,所以课程包和毕业论文最常拿它们做对比实验。选哪个,直接决定后面脚本怎么写、指标怎么解释。

我用一个对比表来说明:

协议路由发现方式是否源路由防环机制控制开销特点适合仿真规模
DSDV周期性全网广播否列向量 + 序列号节点越多开销越稳定小规模、移动较慢
DSR按需,缓存路由是源路由 + 缓存校验移动频繁时缓存命中率高中规模,适合链式拓扑
AODV按需,逐跳维护否目标序列号拓扑变化越大开销越高中大规模、移动性随机

DSDV 的问题在于周期性广播,节点一旦多起来,控制报文占比会非常高。DSR 的优势是源路由,中间节点缓存里能直接命中,但每个数据包头都要带完整路径,包越长,在低带宽无线链路上的浪费越明显。AODV 站在两者中间:按需发现节省了静默状态下的开销,逐跳路由又没有源路由的包头开销,所以它成为绝大多数自组网仿真项目的默认选择。

从工程复现的角度,AODV 还有两个现实优势。一是 ns-2 自带的 AODV 实现经过了大量论文验证,网上能找到的 trace 分析脚本、对比数据都基于它,你有参照物可以对照。二是 AODV 是 IETF RFC 3561 标准协议,就算你后续迁移到 ns-3、OMNeT++,协议行为也是同一套,学习投入不浪费。对于课程设计里常做的“AODV 与 DSDV 性能对比”,AODV 作为主协议、DSDV 作为参照协议,是非常稳妥的组合。

2.3 第一批必设参数:节点数、面积、移动速度、流量模式

自组网仿真里,场景参数不是随便填的。AODV 能不能建立路由、建立之后能不能稳住,取决于节点密度、运动速度和流量分布。我的经验是,拿到一个项目先别急写代码,先把下面这组参数定下来:

参数常用值说明
节点数30-60太少拓扑不连通,太多 MAC 层冲突严重
仿真面积600×600 到 1000×1000面积决定节点密度
移动模型Random Waypoint经典默认模型
节点速度1-20 m/s速度越高路由断链越快
暂停时间0-30 s0 表示持续移动
传输范围默认约 250 m取决于物理层模型
流量模型CBR over UDP最便于统计丢包和时延
仿真时长50-200 s太短冷启动影响大

这里有一个我在课程验收里见过无数次的坑:节点数 50、面积却设成 2000×2000,结果平均邻居数连 2 都不到,AODV 的 RREQ 发出去根本没人响应,PDR 直接是 0。自组网要能工作,节点密度必须先满足网络连通性。802.11 节点传输范围 250 米时,在 1000×1000 的区域内放 50 个节点,平均每跳覆盖半径大约 125 米,网络还能连通;如果这个密度再减半,基本就是孤立节点集合。

节点移动速度也是关键变量。AODV 是反应式协议,速度从 5 m/s 提到 20 m/s,断链频率会明显增加,RREQ 重发次数和 RERR 报文数量都会上升。做性能对比实验时,固定其他条件、只扫速度这一维度,是很容易出趋势的题目。流量模式建议用 CBR over UDP,因为 TCP 的拥塞控制会干扰路由层表现,你很难把“丢包”归因到路由还是传输层。

3. 在 ns-2 里把 AODV 仿真跑起来:最小命令与 TCL 场景

3.1 安装环境并确认 AODV 模块存在

移动自组网仿真最传统的平台是 ns-2,版本以 ns-2.35 最为常见。AODV 作为内置路由协议,源码在 ns-2.35 的aodv目录下,并不需要额外下载。但实际课程包里经常带着一份别人改过的 aodv 源码,比如加了能量模型、修改了 hello 机制,这就需要你把它覆盖到源码目录里重新编译。

先做环境检查,命令如下:

# 检查 ns 命令是否可用 which ns ns -version # 如果 ns 不可用,在 Ubuntu/Debian 上直接装二进制包 sudo apt-get install ns2 # 确认 AODV 模块文件存在 find /usr -type d -name aodv 2>/dev/null ls /usr/lib/ns2/aodv 2>/dev/null

安装完成后,重点看两样东西:aodv.cc和aodv.h是否编译进了解释器,以及 TCL 脚本里能否用Agent/AODV这个类名。我一般会在 TCL 脚本里加一行puts "$ns info class Agent/AODV"来验证,能输出版本信息就说明 AODV 类加载成功。如果你拿到的是 aodv.rar 这类源码包,解压后先看包里有没有Makefile.in改动记录,或者直接按make clean && make重新编译,避免旧.o文件没被替换而导致运行时报 invalid command。

# 从源码目录重新编译 ns-2.35 cd ns-2.35 ./install 2>&1 | tee build.log # 确认 aodv.o 已生成 ls -l aodv/aodv.o

编译这一步最容易出问题的是 GCC 版本过高。老项目基本是按 GCC 4.x 写的,在 Ubuntu 20.04 以上的环境直接 make 会碰到 C++ 语法报错。常见做法是安装gcc-4.8并指定CXX=g++-4.8重新配置,或者干脆用 apt 装现成的ns2包,只要不再改 AODV 源码,自带模块就够跑。

3.2 写一个 50 节点随机路点的 AODV 仿真 TCL 脚本

下面这个 TCL 脚本是一个“最小可出数据”的 AODV 自组网仿真场景。50 个节点在 800×800 米区域内随机分布,CBR 流量共 10 条流,仿真时长 60 秒,路由协议指定为 AODV。这个脚本基于 ns-2.35 的老式 trace 格式编写,便于后续用 awk 直接统计。

# 移动自组网 AODV 最小仿真场景 # 50 个节点,800x800 米,Random Waypoint 移动,10 条 CBR 流 set val(nn) 50 set val(x) 800 set val(y) 800 set val(stop) 60 set val(rp) AODV set val(ifqlen) 50 set ns [new Simulator] set tracefd [open aodv_tr.tr w] $ns trace-all $tracefd set namtrace [open aodv_nam.nam w] $ns namtrace-all-wireless $namtrace 800 set topo [new Topography] $topo load_flatgrid $val(x) $val(y) create-god $val(nn) # 物理层与 MAC 层参数,保持默认既有范围 Phy/WirelessPhy set bandwidth_ 11Mb Mac/802_11 set basicDataRate_ 1Mb Mac/802_11 set dataRate_ 11Mb # 无线移动节点统一配置 $ns node-config -adhocRouting $val(rp) \ -llType LL \ -macType Mac/802_11 \ -ifqType Queue/DropTail/PriQueue \ -ifqLen $val(ifqlen) \ -antType Antenna/OmniAntenna \ -propType Propagation/TwoRayGround \ -phyType Phy/WirelessPhy \ -channelType Channel/WirelessChannel \ -topoInstance $topo \ -agentTrace ON \ -routerTrace ON \ -macTrace OFF # 创建节点并随机初始化位置 for {set i 0} {$i < $val(nn)} {incr i} { set node_($i) [$ns node] $node_($i) set X_ [expr {int(rand()*$val(x))}] $node_($i) set Y_ [expr {int(rand()*$val(y))}] $node_($i) set Z_ 0 } # 每个节点在 0-20 秒内随机选择新地点,以 5m/s 移动 for {set i 0} {$i < $val(nn)} {incr i} { set xd [expr {int(rand()*$val(x))}] set yd [expr {int(rand()*$val(y))}] $ns at [expr {int(rand()*20)}] "$node_($i) setdest $xd $yd 5" } # 10 条 CBR 流:源节点 0-4,目标节点 10-19 for {set i 0} {$i < 10} {incr i} { set src [expr {$i % 5}] set dst [expr {10 + $i}] set udp [new Agent/UDP] $ns attach-agent $node_($src) $udp set null [new Agent/Null] $ns attach-agent $node_($dst) $null $ns connect $udp $null set cbr [new Application/Traffic/CBR] $cbr set packetSize_ 512 $cbr set interval_ 0.25 $cbr attach-agent $udp $ns at [expr {5 + $i}] "$cbr start" $ns at $val(stop) "$cbr stop" } proc stop {} { global ns tracefd $ns flush-trace close $tracefd exit 0 } $ns at $val(stop) "stop" $ns run

这个脚本最关键的是node-config必须在创建节点之前执行,否则后续节点不会带上无线接口,trace 里全是NO TYPE之类的报错。ifqType我特意写的是Queue/DropTail/PriQueue,这是 AODV 仿真的一个隐形要求:路由发现期间数据包要在节点队列里等待,AODV 的控制报文需要优先出队。如果你换成普通 DropTail,AODV 的 RREQ 可能被数据包堵在队列尾部,导致路由发现超时。

create-god是 ns-2 无线仿真里的全局调度实体,参数必须和节点数一致。位置随机生成用了 Tcl 的rand(),每次运行都会不同,这其实是好事——多次跑取平均能消掉随机性影响。如果需要完全可复现,可以加一行ns-random 1固定种子,或者用 setdest 生成固定移动轨迹。

3.3 运行脚本并检查 trace 输出

把脚本保存为aodv_sim.tcl,在终端执行:

ns aodv_sim.tcl # 查看输出文件 ls -lh aodv_tr.tr wc -l aodv_tr.tr # 看 AODV 控制报文是否真的出现在 trace 里 grep "aodv" aodv_tr.tr | head -n 5

正常运行时,aodv_tr.tr会很快增长到几十万行。grep 输出应该能看到 RREQ、RREP、RERR 等报文记录。如果 trace 里只有 cbr 数据而没有 aodv 控制报文,说明路由协议类没加载成功;如果完全没有AGT层收包记录,则说明节点间没有建立任何链路,要去查密度和传输范围参数。这一步跑通了,数据的处理才有意义。

4. 从 aodv_tr.tr 到 PDR、平均时延、路由开销:awk 统计一步步拆

4.1 trace 行结构与 AODV 报文的识别

ns-2 的老式无线 trace 每行有固定字段,我常用的判断规则是:$1表示事件类型,s是发送,r是接收,d是丢弃,+是入队,-是出队;$2是时间戳;$3和$4是收发节点编号;$5是报文类型。比如 cbr 数据报文、aodv 控制报文。

一个典型的 AODV 控制行长这样:

r 1.025314 _8_ RTR --- 0 AODV 48 [0 0 0 0] 0 0 [0 8 0 0] 0 8

其中_8_是节点编号,RTR表示报文发生在路由层,AODV是协议类型。统计路由开销时,直接数AODV行的数量即可。数据报文则是AGT层产生、类型为cbr的行。统计前先确认你用的是老式 trace 格式,也就是 TCL 里不要加$ns use-newtrace。新格式字段顺序不同,脚本要跟着改,容易乱。

4.2 用 awk 计算 PDR、平均时延和丢包数

下面的 awk 脚本按“发送 cbr 时记录时间,接收 cbr 时匹配序列号计算时延”的思路统计。需要注意,$11在 ns-2 老式 trace 中是序号字段,同一序号如果发生重传,后发送的时间会覆盖先发送的时间,这在 CBR 流里基本可忽略。

awk ' { event = $1 type = $5 seq = $11 time = $2 if (event == "s" && type == "cbr") { send_time[seq] = time send_ct++ } if (event == "r" && type == "cbr") { recv_ct++ if (send_time[seq] != "") { delay_sum += time - send_time[seq] delay_ct++ } } if (event == "d" && type == "cbr") { drop_ct++ } if (type == "aodv") { aodv_ct++ } } END { if (send_ct > 0 && recv_ct > 0) { pdr = recv_ct / send_ct avg_delay = delay_sum / delay_ct } else { pdr = 0 avg_delay = 0 } printf "send=%d recv=%d drop=%d PDR=%.4f avg_delay=%.4f AODV_ct=%d\n", send_ct, recv_ct, drop_ct, pdr, avg_delay, aodv_ct } ' aodv_tr.tr

这段 awk 的逻辑不复杂,但有一些细节要说明。send_ct统计的是 AGT 层的 cbr 发送,不是 MAC 层发送;同一数据包在路由转发过程中会被中间节点再发送一次,但那一行发生在新 trace 没法逐跳级里,统计时只认 AGT 层,否则 PDR 会被重传数据拉高。delay_sum用的是接收时间减去源节点发送时间,这是端到端时延;如果还想拆出路由发现时延,就得单独对第一个 RREQ 到 RREP 的时间段做分析。

路由开销用AODV_ct除以recv_ct或者除以send_ct,得出“每成功收到一个数据包需要多少控制报文”。这个比值是自组网论文里很常见的一个指标,但它必须说明统计范围,因为 RREQ 每跳转发都会计一次,跳数越多开销越大。

4.3 批量跑多个场景,用 Python 画出趋势

单次仿真的 PDR 其实没有太大说服力。我会固定随机种子跑三种不同协议,或者固定协议扫三组节点数,把数据汇总后画折线。这里给出一个简单的 Python 脚本,用来读取多组结果并绘出 PDR 趋势:

import matplotlib.pyplot as plt x = [30, 50, 70] pdr = [0.65, 0.78, 0.71] plt.plot(x, pdr, marker="o") plt.xlabel("node count") plt.ylabel("packet delivery ratio") plt.title("AODV simulation") plt.grid(True) plt.savefig("pdr_trend.png")

画图本身不是重点,重点是每次跑完要保留三样东西:TCL 脚本、trace 文件和 awk 计算结果。很多项目最后被导师追问数据来源时,都是因为只留了截图没留 trace,结果无法复现,只能重跑。我自己的习惯是每次改参数前先备份一份脚本,文件名里带节点数、速度和流量数,例如aodv_n50_s5_f10.tcl,这样批量做参数扫描时才不会把结果和配置对应错。

5. 自组网 AODV 仿真避坑清单:5 次踩坑记录与处理方式

5.1 PDR 为 0:场景密度根本没连通

现象:仿真跑完,trace 文件很大,但 awk 统计的 recv_ct 是 0,send_ct 有几千。打开 NAM 能看到节点各自移动,却几乎没有数据包到达。

原因:最常见的两个,一是场景面积太大、节点太稀疏,AODV 的 RREQ 广播找不到目标;二是移动节点创建时没有正确设置X_、Y_,所有节点初始位置都堆在(0,0)附近,早期链路建立后节点迅速移出传输范围,后续路由全部失败。

解决:先算一下节点平均邻居数。800×800 区域、50 节点、传输范围 250 米时,理论平均邻居数约 15 个,够用;面积不变、节点降到 20,平均邻居数不到 6,AODV 基本就是半瘫痪。遇到 PDR 为 0,第一件事不是改 AODV 参数,而是把面积缩小到 600×600,或者把节点数提到 50 以上,把密度问题先解决。

5.2 trace 里全是 RREQ、没有 RREP:路由发现活着但找不到目标

现象:grep 统计发现 AODV 控制报文里 RREQ 占了 95% 以上,RREP 极少,CBR 根本没流量。仿真日志里还能看到timeout waiting for RREP之类的超时记录。

原因:目标节点不可达,或者 RREQ 被 MAC 层丢弃。后者在节点密集时尤其明显:RREQ 广播会造成广播风暴,MAC 层冲突加剧,RREQ 还没发出去就被丢弃。另一类原因是 HELLO 机制和链路检测参数不匹配,节点判定邻居在线,但实际链路质量已经差到数据帧传不过去。

解决:先把节点数降下来,确认密度不是过度拥挤。然后调整 AODV 类属性,比如在 TCL 里加一行Agent/AODV set hello_period_ 1000,缩短邻居探测周期,让节点更快发现链路断了去重新找路。如果还不是,就把ifqLen从 50 提到 100,因为 RREP 回来时如果队列已满,也会被直接丢弃,结果就是 RREQ 反复重发。

5.3 PDR 超过 100%:统计口径把控制报文算进去了

现象:awk 算出来的 PDR 是 1.3,也就是收到了 130% 的“发送量”,一看就不合理。检查 trace 才发现,自己把 MAC 层重传和 AODV 控制报文也放进了接收计数。

原因:统计时没有只认 AGT 层的 cbr 发送行。AODV 的控制报文同样是r事件,如果只看事件和报文类型而不限制层,控制报文就会被当作数据报文。另外,多跳传输中同一个数据包会在每个中间节点产生一条+、-、r记录,中间节点的r不是最终接收。

解决:awk 里严格加两个条件:$1 == "s" && $5 == "cbr"统计发送、$1 == "r" && $5 == "cbr"统计接收,并且确认这些行的层字段是 AGT 而不是 RTR 或 MAC。如果 trace 里层信息不好判断,就只统计带AGT标记的行。统计前先跑一个 2 节点直连的最小场景,手动验证 PDR 应该等于 1,再上大场景。

5.4 多组实验跑出来数据一模一样:随机种子没有变化

现象:改了几次节点移动速度,PDR 和时延曲线几乎完全一致,怀疑是自己脚本写错了。实际上是因为每次运行都用了相同的随机种子,连移动轨迹、流量启动时间都完全相同,唯一变化只有速度参数,而该参数对拓扑没有影响。

原因:ns-2 里的rand()默认由同一个全局随机源驱动,TCL 脚本没有重新播种时,每次ns执行产生的随机序列是同一份。多组对比实验里,如果只改速度但移动轨迹还是一样的,结果的差异只会来自路由层对速度的反应,而不是拓扑随机性。

解决:在 TCL 脚本开头加一行ns-random [expr {int(rand()*10000)}],或者直接给每个实验指定不同种子值。更严谨的做法是用 setdest 工具先生成多个移动场景文件,例如生成mob1.tcl、mob2.tcl,再在仿真脚本里通过变量source mob1.tcl加载。这样拓扑变化和速度变化是解耦的,后续归因才说得清。

5.5 node-config 顺序错误导致节点无无线接口

现象:脚本看起来没问题,但运行时报错,提示节点不存在setdest操作,或者 trace 行中完全没有 MAC 层记录。更隐蔽的情况是 NAM 里所有节点都画成普通有线节点。

原因:$ns node-config语句放在了节点创建之后。ns-2 的无线节点属性在创建那一刻就被固化,之后修改配置不会影响已创建的节点。这个问题在很多派生课程包里都有,因为作者把 node-config 和参数初始化写在不同 section,复制粘贴时顺序丢了。

解决:务必按照“先全局参数、再 node-config、再创建节点、再设置移动和流量”的顺序组织脚本。我把这条贴在实验日志第一页:如果新建的节点没有带无线接口,先看 node-config 有没有跑到for循环前面。这个问题排起来非常费时,因为报错往往不在第一屏,而是在几十秒后才暴露出来。

6. 把多组 AODV 结果做成可信结论:批量跑、稳定性校验和 ns-3 出路

仿真项目的验收,通常不只问“出了什么数”,更会追问“这个数可信吗”。我现在的固定做法是:同一组参数跑 5 个不同随机种子,取平均值和方差。如果 PDR 在 3 个不同种子下波动超过 0.1,就说明场景本身不够稳定,增加仿真时长或者调整节点密度后重跑,而不是直接拿单次结果去写报告。

批处理可以这样写:

for seed in 1 2 3 4 5; do ns-random $seed ns aodv_sim.tcl awk -f calc.awk aodv_tr.tr >> result_${seed}.csv done

稳定性校验还有一个更快的办法:在同一个场景里,只改路由协议,把 AODV 换成 DSDV 或 DSR,看 PDR 是否有明显变化。如果 AODV 和 DSDV 结果几乎一样,通常是场景太简单、节点基本静止,协议的按需优势根本没发挥出来。这时候要加大移动速度或场景面积,让路由协议真正面对拓扑变化。这个对照实验做出来,整篇报告的说服力会高不少。

如果 ns-2 在你当前系统上始终编译不过,学校作业又有交源码的要求,可以考虑迁移到 ns-3。ns-3 里有完整的 AODV 模块,接口比 ns-2 干净,trace 采集用pcap或 flow monitor 都更省事。但要注意,ns-3 的 AODV 和 ns-2 的 AODV 在默认参数上不完全一致,跨平台对比时不能直接说“相同协议在不同版本结果必然一致”,要在论文里说明两套环境的差异。AODV 作为经典协议,跑通它并不难,难的是让每个数据都能被追溯、每个参数都有据可查。我自己吃过最大的亏,就是一次实验里改了三个变量却只留了一组结果,答辩时被问得哑口无言。自那以后每次跑仿真都会留一份带完整参数的日志。这个习惯,希望能帮你少走一点弯路。

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

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

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

立即咨询