
简介针对移动自组织网络MANET中路由协议仿真需求提供一套基于AODV协议的Matlab实现适用于高校通信、计算机、电子信息等专业学生的课程设计、期末大作业和毕业设计也可作为研究人员验证路由性能的轻量工具。压缩包共4个文件包含2个m脚本、1张网络仿真结果示意图和1份md说明文档整体体积仅67KB结构简明函数模块划分清晰便于二次开发。目前已有91人学习下载。代码采用参数化编程兼容Matlab2014/2019a/2024a可灵活调整节点移动速度、通信范围、网络规模等参数直观观察AODV在按需路由发现RREQ/RREP与失效维护RERR过程中的行为并统计端到端时延、丢包率及吞吐量等核心指标。附赠案例数据支持直接运行可快速复现典型MANET场景帮助用户深入理解动态拓扑下AODV协议的运行机制与性能表现。1. AODV 路由模拟 MANET为什么这套 Matlab 代码值得拆开看移动自组织网络MANET最大的特点是“没有基站也能活”节点一边移动一边互相转发数据包路由表随时可能失效。在众多路由协议里AODVAd Hoc On-Demand Distance Vector是教学和科研中最常被拿来开刀的按需协议平时不维护路由要发包才临时广播 RREQ收到 RREP 就建立路径链路断了再用 RERR 通知上游。这套 Matlab 源码把 AODV 的路由发现、路由维护和性能统计封装成了参数化仿真框架支持 Matlab 2014/2019a/2024a附带的案例数据可以直接跑通。适合两类人做课程设计、毕业设计的学生以及想快速验证“节点密度、速度、通信半径对网络性能影响”的研究者。我这里不打算复述 README而是从协议状态、事件驱动、参数调优、性能统计口径和 Profiler 调优五个角度把它拆成可以自己改、自己扩展的代码骨架。2. AODV 协议状态机与 Matlab 中的事件驱动建模2.1 从 RREQ/RREP/RERR 看 AODV 的核心机制AODV 不是时刻都在计算路由而是“需要时才找”。当源节点有数据要发给目的节点但路由表里没有有效条目时它会生成一个 RREQ 包里面包含源地址、目的地址、广播 ID、跳数计数等字段然后向邻居广播。邻居收到 RREQ 后如果自己不是目的节点也没有到目的节点的新鲜路由就把跳数加一继续广播。这里最容易出错的是广播 ID 的处理每个节点维护一个序列号只有“源地址 广播 ID”组合第一次出现时才转发否则直接丢弃。这套 Matlab 代码里用一个结构体数组来存“最近收到的 RREQ 记录”比对逻辑就在handleRREQ函数里。目的节点或拥有新鲜路由的中间节点收到 RREQ 后回复 RREP 报文。RREP 是单播的沿着 RREQ 经过的路径反向回传。每一个收到 RREP 的节点都会在自己的路由表中建立“目的地址 - 下一跳 - 跳数”条目同时为这个条目启动一个生命周期定时器。AODV 的“按需”就体现在这儿路由条目不是周期刷新的而是靠数据包和 RREP 的到达来续期。如果某条链路上的节点移动导致 RREP 无法回传或者数据传输时发现下一跳不可达节点就生成 RERR 报文发给所有受影响的上一跳节点。在仿真里要还原这套逻辑至少需要三种事件RREQ 广播事件、RREP 单播事件、数据包发送事件。事件可以通过定时器触发也可以通过“收到包”触发。代码里常见的做法是用一个events结构体数组模拟事件队列每个事件包含time、type、src、dst、payload字段主循环每次取最小time的事件执行这样就把连续时间离散化成事件驱动避免了用固定步长模拟导致的精度浪费。2.2 参数化建模把移动模型和通信半径改成你要的场景这套代码的核心卖点是“参数化编程”也就是把网络规模、移动速度、暂停时间、通信半径、仿真时长全部抽成脚本开头的变量。比如% 网络规模与场景 numNodes 30; % 节点数量 areaSize [1000, 1000]; % 仿真区域单位米 simTime 300; % 仿真总时长单位秒 % 移动模型参数随机路点 Random Waypoint minSpeed 0; % 最小速度 m/s maxSpeed 10; % 最大速度 m/s pauseTime 5; % 到达目标点后的暂停时间 s % 通信参数 commRange 150; % 节点通信半径米超过该距离则不能直连 packetInterval 1.0; % 源节点两次发包间隔秒这里的commRange直接决定了网络的连通性。我一般会建议先算一下理论邻居数在均匀分布下一个节点的平均邻居数量约为numNodes * pi * commRange^2 / (areaSize(1)*areaSize(2))。如果这个值小于 2网络会频繁分割AODV 会疯狂广播 RREQ丢包率飙升。相反如果 commRange 超过 300 米的场景下邻居数过多广播风暴会明显增加延迟。代码里通过initNetwork函数生成节点初始位置用随机路点模型更新每个时间步的位置这部分参数改动不需要动协议逻辑只需要改上面的变量即可。2.3 节点状态与路由表的数据结构设计了解协议后最重要的就是看懂代码里如何表示节点和路由表。这套 Matlab 代码没有用面向对象的 classdef而是采用结构体数组这是老版本 Matlab 最常见的写法也更容易被学生看懂。典型的节点结构体如下nodes(i).id i; nodes(i).pos [x, y]; % 当前坐标 nodes(i).speed 0; % 当前速度 nodes(i).routeTable []; % 路由表每行: [dest, nextHop, hopCount, seqNum, expireTime] nodes(i).neighbors []; % 当前邻居 ID 列表 nodes(i).rreqSeen []; % 已收到的 RREQ 记录每行 [src, rreqId] nodes(i).buffer []; % 等待路由时的数据包缓存路由表用矩阵表示每行五个字段查找路由时用find(nodes(i).routeTable(:,1) dst)。expireTime用来判断路由是否新鲜AODV 规定每个路由条目都有生命周期超过后就不能使用需要重新发 RREQ。rreqSeen是为了避免同一个 RREQ 被重复广播它记录的是“源地址 广播ID”的组合这样就能防止环路。这套数据结构在节点数小于 200 时效率完全够用但如果你要跑 500 节点以上的场景就要考虑把路由表改成containers.Map否则find会变成 O(N) 的线性扫描拖慢仿真速度。我在第 5 章会给出一个具体的 Profile 定位和优化方案。3. 直接能跑的仿真框架主循环、事件调度与性能统计3.1 主脚本 Block.m 的运行流程拿到压缩包后第一步不是直接运行而是先看Block.m的结构。这个脚本是仿真入口它的典型流程是初始化参数 - 生成节点和移动轨迹 - 开始主循环 - 处理事件 - 收集统计量 - 画图。我这里给一个简化但能对应到源码的主循环框架% 初始化 [network, events, stats] initScenario(numNodes, areaSize, commRange); currentTime 0; while currentTime simTime % 1. 取出最早的事件 [nextEvent, idx] minEvent(events, currentTime); if isempty(idx), break; end % 2. 推进节点位置到事件时间 for i 1:numNodes network.nodes(i).pos moveNode(network.nodes(i), currentTime, nextEvent.time); end currentTime nextEvent.time; % 3. 更新邻居关系只在拓扑变化时重算避免每步全量计算 network updateNeighbors(network, commRange, currentTime); % 4. 根据事件类型分发处理 switch nextEvent.type case DATA handleData(network, events, nextEvent); case RREQ handleRREQ(network, events, nextEvent); case RREP handleRREP(network, events, nextEvent); case RERR handleRERR(network, events, nextEvent); end % 5. 删除已处理事件加入新产生的事件 events(idx) []; end % 6. 输出统计结果 showReport(stats);这里的关键设计是“事件时间驱动”而不是“固定步长驱动”。如果采用固定步长比如每 0.01 秒扫一遍所有节点那么当网络空闲时会造成大量无效循环而且时间精度受步长限制广播延迟会被系统性地高估。事件驱动则只在真正有事情发生的时刻做计算。moveNode函数负责随机路点模型的运动插值。节点先随机选一个目标点以匀速朝目标移动到达后暂停pauseTime再选下一个目标点。因为位置是在事件间隔内插值得到的所以节点移动是连续而非突跳的这符合 MANET 的假设。3.2 关键函数的行为与调用关系除了主循环你需要重点读这三个函数handleRREQ收到 RREQ 后先检查rreqSeen是否处理过同源同 ID 的请求。如果是重复广播直接丢弃否则尝试在路由表里找目的节点的有效路由。找到就回 RREP找不到且自己不是目的节点就继续转发 RREQ转发时跳数加一并把原始请求的源、ID、上一跳地址记录到自己的rreqSeen里。handleRREP收到 RREP 后更新路由表。AODV 规定RREP 中的目的序列号如果比当前记录的新或者序列号相同但跳数更短就更新路由条目。这套代码里用updateRoute函数实现这个比较逻辑这是最容易写错的地方——很多人直接覆盖结果导致旧路由把新路由顶掉。handleData从源节点取一条数据包查路由表。有有效路由就发送给下一跳没有就缓存进buffer并发起一次 RREQ等待 RREP 后再继续发送。这个等待机制就是 AODV 的“按需”本质数据包不会因为暂时没路由就被丢弃而是等待路由建立。这三个函数里最耗时的是handleData中的路由查询。如果频繁调用find在全局变量里寻找性能会急剧下降。更好的做法是在每个节点结构体里单独维护路由表这样查询范围缩小到单节点。3.3 性能指标怎么统计延迟、丢包率、吞吐量的计算口径代码附带的案例数据可以直接运行但你要清楚统计指标是怎么算的否则把仿真结果写进论文时容易被审稿人问住。常见的统计口径如下指标计算方式注意点端到端延迟数据包到达目的节点的时间 - 数据包生成时间只统计成功到达的包不含被丢弃的包丢包率1 - 成功接收的数据包数 / 源节点发送的数据包总数这里的“发送”指应用层产生不包含重传吞吐量成功接收的数据字节数 / 仿真时长单位通常为 Kbps注意和链路带宽区分路由开销全网所有控制包RREQ/RREP/RERR数量 / 成功接收数据包数量衡量 AODV 按需机制是否划算代码里通常用三个计数器sentPackets、receivedPackets、rreqCount。在handleData中生成数据包时sentPackets sentPackets 1在目的节点收到时receivedPackets receivedPackets 1。延迟的计算则要求每个数据包结构体里带有birthTime字段目的节点收到时用now - birthTime累加。这里有个常见的统计陷阱如果仿真中目的节点移动离开了通信范围数据包永远无法到达此时丢包率会异常偏大。合理的做法是把“目的节点不可达”和“路由失败”区分开但许多课程设计代码并不区分导致你看到的丢包率其实是两种原因的叠加。我一般会在跑对比实验时把网络分割严重的场景剔除或者单独统计“由路由错误导致的丢包率”这样结果更容易解释。4. 把 AODV 用出花来实验设计与参数调优4.1 实验矩阵速度、节点数、通信范围对协议性能的影响AODV 仿真最常见的目的是观察不同参数下的性能变化。这套代码的变量都已经参数化所以你可以设计一个三因素实验矩阵。我建议把每个因素取 3 个水平总共跑几次而不是盲目地穷举。% 实验矩阵示例节点数 20/40/60最大速度 2/10/20通信半径 100/150/200 paramSets struct( ... numNodes, {20, 40, 60}, ... maxSpeed, {2, 10, 20}, ... commRange, {100, 150, 200}); for k 1:length(paramSets) numNodes paramSets(k).numNodes; maxSpeed paramSets(k).maxSpeed; commRange paramSets(k).commRange; [stats(k)] runSimulation(numNodes, maxSpeed, commRange, simTime); fprintf(N%d, Speed%d, Range%d: 延迟%.2fms, 丢包率%.2f%%\n, ... numNodes, maxSpeed, commRange, stats(k).avgDelay*1000, stats(k).lossRate*100); end跑完你会观察到几个典型现象节点移动速度增大时丢包率会先小幅上升然后急剧上升原因是路由断链频率增大RERR 通知来不及传递通信半径增大时延迟和丢包率通常下降但路由开销RREQ 数量会上升因为邻居增多导致广播覆盖范围变大。记住不要只盯一个指标AODV 是“用开销换连通性”开销与性能的权衡才是 MANET 研究的重点。4.2 对比实验AODV 与泛洪、静态路由的差异分析为了体现 AODV 的价值你还需要设计对照协议。最简单的对照是“纯泛洪”和“静态路由”。纯泛洪不需要路由表每个数据包直接广播全网但只允许目的节点接收。这个方案在节点数少时延迟低但随着节点增多网络拥塞指数级上升。静态路由则是预先计算好固定路径不随节点移动更新适合模拟理想化场景或作为性能上限参考。代码中实现静态路由很直观% 预先计算最短路径生成静态路由表 for i 1:numNodes for j 1:numNodes path dijkstra(network.adjacency, i, j); if ~isempty(path) nodes(i).staticRouteTable(j, :) [j, path(2), length(path)-1]; end end end对比时要注意静态路由在拓扑不变的条件下表现最好一旦节点移动超过通信半径所有经过断链的数据包都会丢失而且无法自愈。AODV 恰恰在这种场景下能通过 RERR 机制重新发现路径所以你重点对比“移动速度0 vs 移动速度15”两种情况。这样既能说明代码正确性也能在课程设计中展示“协议适应性”这个关键结论。4.3 常见坑路由环路、过期路由、边界效应AODV 仿真中我见过最多的三个问题就是路由环路、过期路由和边界效应。路由环路大多出现在 RREP 反向路径未被正确清除时。如果节点 A 向 B 发 RREQB 的 RREP 回传经过 C此时 C 的路由表已经更新为通往 B 要经过 A旧路由那么 RREP 会原路返回导致 C 和 A 之间形成环。AODV 协议标准里用目的序列号来抑制环路RREP 携带的序列号必须比路由表中记录的更新否则不能更新。代码里updateRoute函数如果漏了序列号比较就会出问题。你在跑动节点场景时如果发现延迟异常大多怀疑路由环路。过期路由是另一个隐蔽问题。AODV 路由条目有生命周期生命周期一到就标记为无效。但很多初学者在handleData里查路由时没有检查expireTime是否大于当前时间导致用已经过期的路由发包产生大量丢包。修正方法是把find出的路由条目再过滤一次。边界效应指节点移动到仿真区域边界时随机路点模型会导致节点在边界附近聚集模拟出的网络密度不均匀。这不是 bug但会使你的统计结果有很大方差。解决方法是把移动模型改成“随机路点带 minimum speed”例如最小速度不低于 1 m/s避免节点长期停在角落。代码里已经在moveNode中加了相关处理你改参数时需要留意注释里的说明。5. 一个实用技巧用 MATLAB 的 Profile 定位仿真瓶颈5.1 开启 Profiler 分析热点函数当节点数提升到 100 以上时仿真速度会明显变慢。这时候不要盲目优化算法先运行 Profiler 找到真正的热点。Matlab 提供了内置的调优工具profile on; runSimulation(150, 15, 150, 300); % 跑一次完整仿真 profile viewer;profile viewer会弹出函数耗时排行表。你大概率会看到moveNode和updateNeighbors占据大部分时间。updateNeighbors之所以慢是因为它用了双层循环计算所有节点对的距离for i 1:numNodes for j i1:numNodes if norm(nodes(i).pos - nodes(j).pos) commRange nodes(i).neighbors [nodes(i).neighbors, j]; nodes(j).neighbors [nodes(j).neighbors, i]; end end end这个 O(N^2) 的操作在 N150 时是 1 万多次距离计算每次调用norm还会创建临时数组开销很大。把它向量化可以提速一个数量级posMat reshape([nodes.pos], 2, []); % 所有节点坐标每行一个点 distMat sqrt((posMat(:,1) - posMat(:,1)).^2 ... (posMat(:,2) - posMat(:,2)).^2); withinRange distMat commRange; % 排除自环 withinRange(1:numNodes1:end) false; for i 1:numNodes nodes(i).neighbors find(withinRange(i, :)); end向量化后原来两次循环变成一次矩阵计算。对于 150 节点的网络这个改动能让单次仿真时间从几分钟降到几十秒。5.2 用距离过滤减少邻居更新频率另一个有效技巧是降低邻居更新的频率。在 AODV 中并不需要每个事件都重新计算邻居关系。只有节点移动距离超过某个阈值时才可能改变连通状态。我一般会在moveNode中记录节点的累计移动距离当累计移动超过commRange * 0.2时才触发该节点的邻居重算其余事件直接用旧的邻居表。% 每个节点增加 moveAccum 字段 nodes(i).moveAccum nodes(i).moveAccum deltaMove; if nodes(i).moveAccum commRange * 0.2 nodes(i).neighbors computeNeighbors(network, nodes(i), commRange); nodes(i).moveAccum 0; end注意这个阈值不能设太大否则两个节点实际已经断开但邻居表还显示连通导致数据包发向不可达节点。commRange * 0.2是一个经验值既保证精度又减少约 80% 的邻居计算量。如果你在跑大规模节点甚至可以把阈值放到commRange * 0.3来换取速度代价是丢包率会略微低估。最后建议你每次改完代码后用固定的随机种子重新跑一遍基准场景确保行为可复现。Matlab 里用rng(42)固定随机数生成器这样不同版本、不同机器之间对比结果才有意义。把这套代码玩透之后你可以继续扩展多业务流、能量模型或者恶意节点检测AODV 作为基础协议能扩展的方向远比你想的多。本文还有配套的精品资源点击获取