
简介面向移动自组网仿真研究的一份AODV协议核心代码资源适合网络方向的研究者、开发者以及学习Ad Hoc路由协议的学生。移动自组网无需固定基础设施节点可动态组网并逐跳转发AODV作为典型的按需路由协议仅在通信需要时才建立路由从而显著减少控制开销并通过RREQ、RREP和RERR三类消息完成路径发现、应答与错误通告。本包内含3个头文件整体仅8KB分别对应协议基本定义与结构体、数据报文封装解析、路由表存储与转发决策路由表模块管理路由项与序列号是判断路由新鲜度和失效链路的基础。已有243人学习过这份资源借助这些头文件可快速理解AODV状态机制与路由表组织方式并扩展仿真实验以分析车载移动自组网下的延迟、吞吐量与路由稳定性。对于应急通信、军事通信等自组网协议优化研究这份资源同样是一个轻量、清晰的起点。1. 打开 aodv.rar 之前先想清楚为什么要碰 AODV 仿真做过移动自组网路由研究的人多半对这类打包好的 aodv.rar 工程不陌生解压之后是一堆 .tcl、.tr、.nam 文件打开主脚本几行set val(rp) AODV指向的正是 Ad hoc On-Demand Distance Vector 协议在 NS2 环境下的标准仿真实现。做移动自组网仿真的人最耗时间的往往不是协议代码本身而是场景生成、参数对齐和 trace 日志解读这三件事。AODV 虽然是二十多年前被提出的按需距离矢量协议今天依旧是 MANET 仿真的默认对照基线任何新路由方案想证明自己有效几乎都得在相同场景下先和 AODV 比一轮。这篇笔记就沿着「协议原理 → 仿真选型 → 最小可运行场景 → 高频翻车点 → 指标验证」这条路径讲目标是让你拿到这类工程后能跑通、能调参、能说服评审。2. AODV 协议机制仿真前必须先立住的三个底层逻辑2.1 按需路由的触发RREQ 泛洪与 RREP 回传的完整闭环AODV 的核心特征是「按需」节点在没有到达目的地的路由时不主动维护全网路由表而是使用广播路由请求Route RequestRREQ来探测路径。整个流程可以拆成四步源节点检查路由表若无有效路由则封装一个 RREQ 并广播RREQ 头部携带源 IP、源序列号、目的 IP、目的序列号、广播 ID 和跳数计数其中「源 IP 广播 ID」唯一标识一次路由发现中间节点收到 RREQ 后先判断是否收到过相同广播 ID 的请求若重复则直接丢弃否则在路由表中建立一条指向源节点的反向路由并继续转发只有两种情况会让中间节点不再转发——它有一条到达目的节点的有效路由且该路由的目的序列号大于等于 RREQ 中携带的目的序列号此时它单播回复一个 RREPRoute Reply给源节点。RREP 回传是整个闭环里最容易被低估的一环。RREP 不是继续广播而是沿着刚才建立的「反向路由」逐跳单播回源节点沿途每个节点再建立一条指向目的节点的正向路由。这个设计避免了洪泛回传但也带来一个后果反向路由的生存时间对仿真结果影响极大。NS2 的 AODV 实现里反向路由由ReverseRouteLife控制默认值较短如果业务流在路由刚建立后就立即要发包而反向路由恰好过期你会在 trace 里看到 RREQ 洪泛次数翻倍但数据包迟迟发不出去的怪象。中间节点回复 RREP 时还隐含一个判据缓存路由的目的序列号必须不低于 RREQ 里的目的序列号。这是 AODV 防环机制的关键。源节点收到多个 RREP 时路由表的更新规则是——目的序列号更大的优先序列号相等时跳数更少的优先。很多仿真新手在分析「为什么我的路径不是最短路径」时忽略了这个规则AODV 优先保证的是序列号新鲜度而不是跳数最优。所以仿真里出现路径不是全局最短跳数是正常行为不该当成 bug 去查。2.2 路由维护的两条腿Hello 消息与 RERR 的触发边界AODV 对活跃路由的维护依赖两个机制周期性 Hello 消息与链路失效通知 RERRRoute Error。每个节点周期性地向邻居广播 Hello 消息本质上是 TTL 为 1 的特殊的 RREP用于宣告「我还活着、链路还在」。邻居节点如果在AllowedHelloLoss个 Hello 周期内没有收到某邻居的 Hello就判定该链路失效。NS2 的 otcl 脚本里agent/rtProto没有直接暴露这个参数的修改它一般要改 aodv.cc 中的HelloInterval_常量后重新编译这属于源码级调参后面避坑章节会细说。链路失效后的动作分两个方向向上游和向下游。下游方向断链处节点的下游邻居若还有通过该链路到达目的地的活跃路由需要删除这些路由上游方向断链处节点会生成 RERR 并向活跃上游邻居广播。每个收到 RERR 的节点删除匹配的目的地路由若它自己也作为上游在使用该路由则继续转发 RERR直到最终通知到源节点由源节点重新发起 RREQ。仿真里常见的「数据流中断几秒后恢复」正是这一整套维护流程在起作用。这里有一个仿真特有的注意事项NS2 的无线信道默认打开 ARP 与 MAC 层重传链路断裂不一定立刻触发 AODV 的 RERR——MAC 层会先重试若干次重试失败后才向上层报告传输失败。所以你在 trace 里看到的丢包时间点往往比物理链路断裂发生的时间晚了几十到几百毫秒。做端到端时延统计时这个时间差会被计入导致结果看起来偏大。我一般会把这个因素写进实验说明而不是在代码里强行补偿。2.3 路由发现重试与拥塞窗口的仿真耦合AODV 有一条在 RFC 3561 里写得很清楚、但在仿真里常被忽略的规则路由发现失败后源节点要等待一个往返超时时间RING_TRAVERSAL_TIME相关的周期然后重试重试次数受ROUTE_DISCOVERY_RETRIES限制。在 NS2 默认实现里RouteRequestRetries的默认值是 2也就是一次业务流最多触发 3 轮路由发现。这个限制在密集网络里问题不大但在稀疏或高移动性场景下三轮 RREQ 全部失败后源节点会直接向上层报告网络层不可达表现为 trace 里 AGT 层连续丢包且没有任何修复动作。更隐蔽的问题是 AODV 的路由发现与 TCP 拥塞控制耦合。移动自组网仿真常用的业务是 CBR/UDP这避开了 TCP 的窗口收缩问题但如果业务换成 TCP 流路由发现期间 TCP 发起的重传和 AODV 的重试会互相踩踏——TCP 重传定时器往往短于 AODV 的ROUTE_DISCOVERY_RETRIES周期导致源节点在路由恢复前就已经重传超时、进入指数退避。这个现象不是 AODV 本身的缺陷而是跨层交互的产物。做 TCP over AODV 仿真的人如果发现 TCP 吞吐量断崖下跌先查路由发现重试周期而不是先怀疑拥塞控制。2.4 选型判断AODV 与 DSDV、DSR、OLSR 的仿真差异选择 AODV 作为移动自组网仿真协议在绝大多数场景下是为了「以按需协议作为对照基线」。和表驱动协议 DSDV 相比AODV 不维护全网路由路由开销更小但在高移动性网络中 RREQ 洪泛次数上升端到端时延波动更大和同是按需的 DSR 相比DSR 在源路由包头里携带整条路径而 AODV 只在中间节点维护下一跳前者的路由开销随路径长度线性增长后者的包头开销恒定更小。OLSR 则走完全不同的链路状态思路适合密集且拓扑相对稳定的场景。如果你做的研究主题是「降低路由开销」或「提升高移动性场景的包投递率」AODV 就是最合适的对照物如果你的主题是「多路径」「网络编码」「机会路由」那 AODV 只适合作为基准不适合作为扩展基础因为它的单路径、目的序列号机制会让扩展改动牵扯到路由表结构和 RERR 逻辑改动量大。这个判断直接在仿真设计的初期阶段做比跑完一轮再返工省力。3. 用 NS2 把最小 AODV 场景跑通从场景生成到 trace 落盘3.1 仿真平台选型为什么这套工程大多落在 NS2 上标题这类 AODV 仿真工程包绝大多数以 NS2 为载体原因很朴素NS2 自 2.35 起就内置了完整的 AODV 实现源码在ns-2.35/aodv/目录下包含aodv.cc、aodv.h、aodv_rtable.cc等文件开箱即用不用自己写协议栈。NS3 当然也能仿真 AODV但 NS3 的 AODV 模块是后来补充的且 NS3 的移动模型和 trace 格式与 NS2 差异较大社区里大量的 MANET 论文、毕设和公开实验都建立在 NS2 的 CMU 无线扩展之上参考资料最厚遇到问题最容易搜到答案。OMNeT 的 INET 框架里同样有 AODV 实现适合需要更细粒度模块改动的场景但配置复杂度和学习成本明显高于 NS2。如果你现在才开始新项目我建议优先考虑 NS3它的 C 代码更现代化、调试工具更好用且对 802.11 模型的仿真精度更高。但如果你手头已经拿到了像 aodv.rar 这类打包好的仿真工程或者是要在已有 NS2 环境上快速复现论文数据直接沿用 NS2 是效率最高的路径。下面所有操作都基于ns-2.35的环境假设这套流程在 Ubuntu 18.04 及相近发行版上验证过。3.2 生成随机路点移动场景setdest移动自组网仿真要有节点移动NS2 自带的随机路点模型生成器是setdest编译后的二进制位于ns-2.35/indep-utils/cmu-scen-gen/setdest/目录下。最常见的生成命令如下cd ns-2.35/indep-utils/cmu-scen-gen/setdest ./setdest -n 50 -p 0 -M 10 -t 300 -x 1000 -y 1000 scenario.tcl参数含义-n 50表示场景中的移动节点数为 50-p 0是暂停时间pause time单位秒0 表示所有节点全程不停歇地移动模拟极端的拓扑变化-M 10是节点最大移动速度m/s速度均匀分布在 0 到这个值之间-t 300是场景持续时间和仿真脚本里的stop时间要一致-x 1000 -y 1000是仿真区域的横向与纵向范围单位米。生成的scenario.tcl内容大致是这样的形式# nodes: 50, pause: 0, max speed: 10.00, max x: 1000.00, max y: 1000.00 $node_(0) set X_ 123.4 $node_(0) set Y_ 567.8 $node_(0) set Z_ 0.0 $ns_ at 0.0 $node_(0) setdest 234.5 678.9 3.2 $ns_ at 100.0 $node_(0) setdest 888.1 77.7 5.0 ...这里X_、Y_是节点初始坐标setdest命令后面跟目标坐标和移动速度。在主仿真脚本里用source scenario.tcl引入这段代码时必须保证节点对象已经创建完成所以source的位置要放在node-config之后、$ns_ run之前。这里有个常见翻车点如果setdest的-t小于主仿真脚本设置的停止时间场景最后阶段所有节点会静止在原地移动模型退化成静态网络平均速度指标直接失真反过来-t大于主脚本的stop时间则尾部的移动指令没有被执行完但不会报错。设置时让两者相等最稳妥。3.3 生成 CBR 业务流chrgen有了移动场景还要有业务流。NS2 自带 CMU 的流量生成器chrgencbrgen它生成的是 CBR恒定比特率业务常用于 MANET 仿真。命令如下ns indep-utils/cmu-scen-gen/cbrgen.tcl -t cbr -nn 50 -seed 1 -mc 20 -rate 4.0 traffic.tcl-t cbr指定流量类型为 CBR-nn 50指定节点总数必须在 setdest 的节点数范围内-seed 1是随机数种子固定下来才能让不同协议运行在相同业务分布上这一点在对比实验里至关重要-mc 20表示随机生成 20 条 CBR 连接每条连接的源、目的节点均匀随机选取-rate 4.0是发送速率单位是包/秒。生成出的traffic.tcl内容形如set udp0 [new Agent/UDP] $ns_ attach-agent $node_(3) $udp0 set null0 [new Agent/Null] $ns_ attach-agent $node_(17) $null0 $ns_ connect $udp0 $null0 $udp0 set packetSize_ 512 set cbr0 [new Application/Traffic/CBR] $cbr0 attach-agent $udp0 $cbr0 set packetSize_ 512 $cbr0 set interval_ 0.25 $ns_ at 10.0 $cbr0 start注意 chrgen 只是把 Agent/UDP 和 Agent/Null 配对真正的发送行为由 Application/Traffic/CBR 控制interval_是发包间隔packetSize_是包大小。如果实际仿真里ns_ at 10.0这个时间早于路由协议收敛前几个数据包会触发 RREQ 洪泛trace 里能看到成批的路由控制包这是正常现象不算异常。3.4 最小 AODV 仿真主脚本逐段拆解主仿真脚本是整套工程的核心。下面这个最小脚本删掉了与参数扫描无关的花哨配置只保留跑通 AODV 的最小集合。# 基本参数定义 set val(chan) Channel/WirelessChannel set val(prop) Propagation/TwoRayGround set val(netif) Phy/WirelessPhy set val(mac) Mac/802_11 set val(ifq) Queue/DropTail/PriQueue set val(ll) LL set val(ant) Antenna/OmniAntenna set val(ifqlen) 50 set val(nn) 50 set val(rp) AODV set val(x) 1000 set val(y) 1000 set val(stop) 300 # 创建仿真器与 trace set ns_ [new Simulator] set tracefd [open out.tr w] $ns_ trace-all $tracefd set namtrace [open out.nam w] $ns_ namtrace-all-wireless $namtrace $val(x) $val(y) set topo [new Topography] $topo load_flatgrid $val(x) $val(y) set god_ [create-god $val(nn)] # 无线节点通用配置 $ns_ node-config -adhocRouting $val(rp) \ -llType $val(ll) \ -macType $val(mac) \ -ifqType $val(ifq) \ -ifqLen $val(ifqlen) \ -antType $val(ant) \ -propType $val(prop) \ -phyType $val(netif) \ -channelType $val(chan) \ -topoInstance $topo \ -agentTrace ON \ -routerTrace OFF \ -macTrace OFF # 创建节点 for {set i 0} {$i $val(nn)} {incr i} { set node_($i) [$ns_ node] } # 引入移动场景与业务流 source scenario.tcl source traffic.tcl # 结束动作 $ns_ at $val(stop) $ns_ nam-end-wireless $val(stop) $ns_ at $val(stop) stop $ns_ at 300.01 puts \end simulation\ ; $ns_ halt proc stop {} { global ns_ tracefd namtrace $ns_ flush-trace close $tracefd close $namtrace exit 0 } puts Simulation starting... $ns_ run这段脚本里最关键的几行set val(rp) AODV决定路由协议-agentTrace ON打开应用层 trace这是后续统计丢包和时延的数据来源-routerTrace OFF关掉路由层逐跳 trace能显著减小 .tr 文件体积如果要做路由开销分析再打开create-god $val(nn)创建的是全局知识节点NS2 用它支撑无线场景的邻居计算节点数必须与val(nn)一致否则运行时直接报错。proc stop {}负责在仿真结束时 flush trace 并关闭文件句柄不做这一步最后一个时间点的事件可能没写进 out.tr导致统计结果缺尾巴。在这些参数里ifqlen 50是接口队列长度直接影响突发业务下的丢包率。队列满了之后即使 AODV 已经建立了路由上层数据包也会在 MAC 层排队时被丢弃trace 里表现为AGT层的包在s事件之后没有对应的r事件。如果你想观察协议在较拥塞信道下的真实表现把这个值降到 10 到 20网络层的丢包会明显增加如果定位是验证路由正确性保持 50 更合适。3.5 运行与快速验证先看 out.tr 前几行在主脚本所在目录执行ns aodv_demo.tcl假设脚本名为 aodv_demo.tcl终端会输出标准 NS2 启动时的各种初始化信息末尾出现 Simulation starting... 后进入静默运行。运行结束后同目录下应当生成 out.tr 和 out.nam。一个正常运行的 AODV 仿真out.tr 应该有如下内容s 0.000000000 _0_ RTR --- 0 AODV 44 [0 0 0 0] ------- [0 0 2] [0/0] [30 0 [2 30] 0] r 0.873910721 _5_ RTR --- 0 AODV 44 [13 5 0 0] ------- [0 0 0] [0/0] [30 0 [2 30] 0] s 0.900000000 _3_ AGT --- 0 cbr 512 [0 0 0 0] ------- [3 0] [0 0] 0 ...第 1 列是事件类型s表示发送r表示接收d表示丢弃第 2 列是事件时间戳第 3 列是节点 ID第 4 列是协议层RTR是路由层AGT是应用层第 7 列是包类型AODV是协议控制包cbr是业务数据包。一套能用的 trace 应该同时包含 AODV 控制包和 cbr 数据包两类记录且数据包从某个s到对应r之间的时间差在几十毫秒量级。看到这类内容说明仿真链路已经通了接下来才能进入调参和统计环节。4. AODV 仿真避坑五条藏得比较深的现场事故4.1 nam 打开后节点一动不动但 trace 里明明有事件运行结束用nam out.nam打开动画发现节点全部静止而你确认 trace 里有大量收发事件——这不是移动模型没生效而是场景文件没有正确载入。常见原因是source scenario.tcl写在node-config之前此时$node_()数组还没创建Tcl 解释器遇到未定义的$node_(0)会直接报错但如果在catch环境下错误被吞掉仿真继续运行只是移动命令全部丢失。解决方法是严格保证node-config与节点创建循环在前source场景文件在后。另一种原因更隐蔽setdest生成场景时使用的主场景尺寸与当前脚本不一致。比如场景在 1500x1500 下生成主脚本里val(x) val(y)是 1000x1000部分节点的初始坐标落在仿真区域之外NS2 的无线模块对越界节点不报错只是不参与邻居发现和数据收发视觉上表现就是大量节点静止在边界外。检查方式很直接打开 scenario.tcl确认所有坐标都在val(x)和val(y)范围内。4.2 业务流运行一段时间后吞吐量直线掉到零且不恢复trace 里能看到前期数据收发正常某一时刻之后 cbr 包全部是s事件而没有r事件但 AODV 控制包仍在出现。这种现象大概率是路由表中所有链路都被标记为失效且本地修复失败后源节点重试也耗尽。先把RouteRequestRetries从默认的 2 调大比如改成 4重试次数不够时高移动性场景下的路由发现失败率会显著上升。但更常见的根源是 MAC 层问题而不是 AODV 问题。NS2 的 802.11 实现里无线接口在多节点同时竞争时会频繁碰撞ifqlen如果设得太小路由发现期间的数据包会在接口队列里被挤掉导致即使路径建立了也没有数据可发。遇到这种状况先看 out.tr 里 AODV 控制包的密度如果 RREQ 包在目标时间段内骤增说明源节点一直在发起新的路由发现那问题在队列或物理层如果 RREQ 包也明显减少说明源节点的重试次数耗尽问题在重试参数。这两类现象让人迷惑的地方在于表面都是吞吐归零处理方向完全相反。4.3 RREP 始终不回传路由一直处于发现状态调试时在 trace 里 grep RREQ 和 RREPgrep RREQ out.tr | head grep RREP out.tr | headRREQ 有大量记录但 RREP 一条都没有——这是反向路由建立失败的典型表现。注意中间节点只有在确认自己是「RREQ 的首次接收者」时才会建立反向路由而判断是否首次接收靠的是「源 IP 广播 ID」二元组。如果场景中节点被设置为初始位置相同比如全部在坐标 0,0多个节点的广播在无线信道中被当作重复帧处理RREP 自然回不来。把 setdest 的随机种子换个值、或手动检查 scenario.tcl 开头的几行坐标是否过于聚集通常能解决。另外一个原因是目的序列号陈旧问题。RREQ 里携带的目的序列号如果比目的节点当前的序列号还大目的节点回 RREP 时会强制把自己的序列号更新为 RREQ 携带值这可能引起路由环如果目的序列号太小中间节点即使有有效路由也会拒绝回 RREP。这类现象在节点长时间断开重连后容易出现检查方式是在 trace 里对比[目的ID 目的序列号]字段。如果每次 RREQ 的目的序列号都比上一次小基本可以判断是初始序列号配置或节点重启逻辑的问题。4.4 trace 里出现超大时间戳或 nan仿真实际已经发散移动自组网仿真里偶尔会遇到一种情况仿真没有报任何错误但 out.tr 末尾出现时间戳为nan或异常巨大的行nam 播放到某一时刻画面卡死。这类异常仿真多半源于坐标溢出——节点的移动目标点由 setdest 生成时取整出错或自定义脚本里让节点在边界外持续移动物理层计算距离时出现非数值结果。排查工具比较直接awk {if ($2 ~ /nan/) print NR: $0} out.tr如果命中回到 scenario.tcl 检查所有setdest调用里的目标坐标。NS2 对坐标不做运行时校验需要脚本作者自己保证坐标在区域范围内这一点没有捷径。时间戳异常还有一个来源是系统时钟被 NTP 校正或虚拟机时间跳变在虚拟机上跑长仿真的话建议把宿主机的自动同步关掉这看着像玄学实际上我碰到过两回都是虚拟化环境引起的。4.5 AGT 层连续丢包提示 no route 却不触发新的 RREQtrace 中应用层包在s事件后不久就被标记为d且在前面有明确的-DROP原因。如果 grep 到RTNroute not found这类标记说明源节点在发送时路由表里确实没有有效路由但它没有重新发起路由发现。这个现象最常见的触发条件是业务启动时间早于路由表建立时间且 RREQ 重试已经耗尽。也就是说源节点在第一个包触发 RREQ、失败、重试、再失败之后后续的数据包全部走RTN丢弃路径不再触发新的 RREQ。解决方案是在主脚本里把业务流的启动时间往后挪观察不同启动时刻对投递率的影响——如果从 0 秒改成 30 秒后指标明显改善说明问题就出在路由预热阶段。做参数扫描时这个启动时间应该作为一个显式变量加入实验矩阵而不是固定在某个值否则你比较的是「是否给路由足够收敛时间」这个隐藏因素而不是路由协议本身的性能差异。5. 进阶用 trace 挖出关键性能指标再让 AODV 参数产生真实差异跑通只是第一步论文或项目验收要的是数字。一个最常用、也最容易复现的指标体系是三件套包投递率PDR、端到端平均时延E2E delay、路由开销NRO。下面这段 awk 脚本从 out.tr 里提取前两个指标BEGIN { send0; recv0; sum_delay0; } { event$1; layer$3; type$7; if (events layerAGT typecbr) { send; send_time[$7]$2; } if (eventr layerAGT typecbr) { recv; sum_delay $2 - send_time[$7]; } } END { printf Send: %d, Recv: %d\n, send, recv; printf PDR: %.2f%%\n, (recv/send)*100; printf Avg E2E Delay: %.3f ms\n, (sum_delay/recv)*1000; }用法是awk -f pdr.awk out.tr。注意$7作为包标识在 NS2 trace 格式里是相对稳定的字段位置但如果你的脚本把macTrace也打开了行字段会变包类型所在列会偏移awk 匹配会失效。我习惯只开-agentTrace ON需要路由开销分析再单独开-routerTrace ON另存一份 trace两个开关混开只会增加解析复杂度。有了这套统计参数调优才有意义。AODV 在 NS2 里有三个参数对结果影响最直观HelloInterval默认约 1 秒决定邻居失效检测速度适当减小到 0.5 秒能加快链路断裂响应但会增大控制开销ActiveRouteTimeout决定活跃路由的有效期在高移动性场景里把默认值调大例如从 3 秒调到 8 秒能减少不必要的路由重建RouteRequestRetries决定路由发现重试次数。调整这些参数需要修改aodv.cc里的默认值后make重新编译 NS2这也是很多人拿到 aodv.rar 后会问「怎么我改了 tcl 没效果」的根源——AODV 的实例变量不是全部暴露给 Tcl 层的一部分只存在于 C 实现里。改完后记得保留原始基线数据AODV 仿真最怕的就是只记录最终版本的结果中间过程全丢评审问起某个数值怎么来的时候完全答不上来。我自己的习惯是每改一次参数就把 out.tr 另存一份文件名带上参数值比如tr_hello0.5_tr131.tr。看起来很笨但在连续跑几十组仿真后这套命名规则能救命的次数远超想象。做仿真这件事八成时间不是花在写代码上而是花在「让实验可重现、可解释、可追溯」上。AODV 这样的二十年前的协议还能霸占 MANET 仿真的基线位置正因为它的行为足够稳定、可预期是天然的对照物——这是好事但也是提醒对照物只有被你真正理解时才有价值。从一张 aodv.rar 开始把 trace 读透把参数调出可解释的差异你才算真正把这个仿真握在手里了。希望帮到你。本文还有配套的精品资源点击获取