
简介这套基于Matlab的AODV路由模拟代码面向通信工程、计算机、电子信息及数学等专业的学生与科研人员用于在移动自组织网络MANET中仿真按需距离矢量路由协议帮助理解动态拓扑下的路由发现、维护与失效恢复机制。代码采用参数化编程支持Matlab 2014/2019a/2024a等版本可灵活调整节点移动速度、通信范围、网络规模等参数观察延迟、丢包率、吞吐量等性能指标。压缩包共4个文件含2个M脚本、1张示意图和1份Markdown说明文档整体仅67KB附带可一键运行的案例数据注释清晰便于课程设计、期末大作业或毕业设计直接参考。目前已有91人浏览学习对于想深入理解MANET路由协议并开展模拟实验的读者这份工具包兼具学习与实用价值。1. 在Matlab里重写AODV是MANET研究绕不开的一步先说一个反直觉的结论用NS2或NS3模拟AODV更省事但如果你要改协议、加模块、和深度学习定位算法做交叉验证Matlab反而是最灵活的那个。很多做MANET移动自组织网络的课程设计和论文第一关就是“把AODV跑起来”而网上流传的Matlab代码包良莠不齐有的连序列号更新都有问题。本文会从AODV的按需路由机制出发拆解一个可在Matlab中直接运行的仿真骨架节点用什么数据结构、RREQ怎么广播、RREP怎么回传、移动模型怎么加、性能参数怎么统计。适合正在做AODV仿真、需要自己掌控每一行逻辑的人而不是只会“双击运行”的纯使用者。2. AODV的3个核心机制序列号、Hello、RREQ/RREP2.1 为什么AODV要维护目的序列号AODV和普通距离矢量协议最大的区别是“按需”没有流量时节点不维护完整路由。每个节点保存一张路由表表里每条路由都绑定一个目的序列号。这个序列号是AODV防环的根本手段——路由更新时只有目的序列号更大或相等但跳数更少的路由才能使用。在Matlab里实现时我建议用结构体数组而不是建立完整类层次。一个节点至少要有这些字段node struct(... id, 0, ... % 节点编号 x, 0, y, 0, ... % 当前位置 vx, 0, vy, 0, ... % 当前速度分量 routeTable, struct(), ... % 路由表用Map存放 seqNum, 1, ... % 本节点自身序列号 queue, {}, ... % 待发送的数据包 neighbors, []); % 邻居表路由表条目我用目的节点ID作为key每个条目记录4个必填字段字段含义更新条件destSeq目的节点序列号收到RREP/数据时取最大值nextHop下一跳节点ID路由发现成功后写入hopCount到目的节点跳数RREQ转发时1lifetime路由寿命每收到对应数据包就刷新这里有个初学者容易踩的坑节点自身序列号是独立递增的不要把目的序列号和源序列号混在一个计数器里。一个节点既是源也是可能的目的各自维护才是正确的。2.2 Hello消息与邻居感知AODV通过周期广播Hello消息感知单向链路。默认每1秒发一次如果连续3次收不到邻居的Hello就认为链路断开需要触发路由错误RERR。在Matlab中Hello消息就是普通的数据结构不必模拟真实以太网帧。关键在“丢包判断”仿真步进时的距离变化会导致链路断裂。我一般用最大传输距离作为判断标准function hello buildHello(srcId, srcSeq, x, y, range) hello.type HELLO; hello.srcId srcId; hello.srcSeq srcSeq; hello.x x; hello.y y; hello.range range; % 最大通信范围比如250米 end % 接收端处理 function carried isNeighborReachable(recvPos, hello) d sqrt((recvPos(1)-hello.x)^2 (recvPos(2)-hello.y)^2); carried d hello.range; end参数说明range是物理层实际传输范围不要和路由协议里的跳数混淆。isNeighborReachable返回0或1在仿真主循环里用来决定是否接受Hello。如果你在真实信道模型里做还需要考虑遮挡和衰落但初版用距离判定足够。2.3 路由发现过程RREQ的扩散与RREP的回传当源节点有数据要发但路由表里没有目的节点条目时发起路由发现。RREQ包被广播给所有邻居邻居收到后要么回复RREP如果它有到目的节点的新鲜路由要么继续广播直到到达目的节点。在Matlab代码里用递归函数表示广播转发最直观function broadcastRreq(srcId, destId, rreq, simNodes, visited) for nid 1:length(simNodes) if visited(nid), continue; end if isNeighborReachable(simNodes(nid), rreq) visited(nid) true; simNodes(nid).queue{end1} rreq; % 入队等待发送 end end end这段代码有一个关键参数visited表。AODV要求同一个RREQ的广播ID只处理一次防止广播风暴。如果你忘记这个标记节点数超过30就会出现指数级重复报文仿真会卡到怀疑人生。目的节点收到RREQ后不是直接回复而是更新自己的路由表中“到源节点”的路由然后单播RREP给源节点。RREP传播过程相对简单因为回传路径已经由RREQ建立。每个中间节点收到RREP时做两件事更新路由表、查是否有正在等待这个目的节点的数据包如果有就发送。3. 用Matlab搭一个最小可跑的AODV仿真场景3.1 初始化场景与节点结构我们不需要工程级别的网络模拟器一个二维平面、固定节点数、随机位置就是最小可行场景。初始化代码如下function simNodes createAndDeployNodes(areaSize, numNodes) simNodes struct([]); for i 1:numNodes simNodes(i).id i; simNodes(i).x rand * areaSize(1); simNodes(i).y rand * areaSize(2); simNodes(i).seqNum 1; simNodes(i).routeTable containers.Map(); simNodes(i).queue {}; end end参数说明areaSize是仿真区域尺寸我通常用[1000, 1000]单位米。numNodes取30到100之间。节点小于30时路由发现几乎用不到多跳转发体现不出MANET特点大于100时Matlab的循环效率会明显下降建议配合parfor优化。3.2 移动模型Random WaypointMANET仿真的默认移动模型是Random Waypoint。每个节点先随机选一个目标点以随机速度移动到达后停留一段时间再选下一个目标。这个模型用四行就能实现% 更新所有节点的位置 for i 1:length(simNodes) if simNodes(i).moving false simNodes(i).target [rand*1000, rand*1000]; simNodes(i).speed 1 rand*9; % 1到10米每秒 simNodes(i).moving true; end direction simNodes(i).target - [simNodes(i).x, simNodes(i).y]; dist norm(direction); if dist 0.1 simNodes(i).moving false; % 到达目的地停一下 else step simNodes(i).speed * dt; simNodes(i).x simNodes(i).x direction(1)/dist * step; simNodes(i).y simNodes(i).y direction(2)/dist * step; end end这里的dt是仿真时间步长单位秒一般取0.1或0.5。步长太小计算量大太大会导致移动过快、链路状态变化失真。speed是每个节点独立随机的如果所有节点同速移动会出现“相对静止”的假象路由稳定度结果会偏高。3.3 生成CBR流量源、目的、发包间隔在AODV模拟里常用持续比特流CBRUDP流量。具体到Matlab就是在主循环中给源节点添加发送任务function packet createDataPacket(srcId, dstId, flowId, seq) packet.type DATA; packet.srcId srcId; packet.dstId dstId; packet.flowId flowId; packet.seq seq; packet.sendTime now; % 记录当前仿真时间不是真实时间 end发送间隔根据速率设置。如果你要模拟“每秒4包”那计数器累加到0.25秒时发送一个包。不要把Matlab里的pause(0.25)当作时间控制那会让真实时间被拖慢性能统计会完全失真。正确方式是维护一个仿真时钟变量每步增加dt然后判断sendTimer 1/rate。3.4 主循环里的“发包-转发-收包”处理主循环要按仿真时间推进而不是按真实时间驱动。我把每次迭代称为一个时隙每个时隙内做这些事情更新节点位置检查现有路由是否过期源节点按计划生成数据包如果查表命中则传给下一跳否则发起路由发现收到RREQ的节点处理并可能转发RREQ收到RREP的节点建立路由数据包在链路上传递到达目的节点后记录时延。其中步骤4查表时一定要同时检查destination sequence number是否足够新。如果目的序列号小于某个阈值视为陈旧路由需要重新发起发现。具体阈值根据协议你设定为一个全局常量REQUIRED_SEQ_DELTA 1。4. 仿真结果怎么统计吞吐量、端到端时延、丢包率4.1 用统计数组而不是表驱动跑一次仿真的原始数据量可能很大我建议在内存中维护三个累加变量而不是每次事件都写日志文件。以下是一个紧凑的统计结构stats struct(... sentPackets, 0, ... receivedPackets, 0, ... delaySum, 0, ... packetDelayArray, []); % 记录每次端到端时延当源节点发出一个数据包时sentPackets加1。当目的节点收到这个包时receivedPackets加1同时用当前仿真时间减去包里的sendTime累加到delaySum。丢包率最终计算为1 - receivedPackets/sentPackets。注意丢包率的统计分母是源节点实际发送数据包的次数不包含路由控制报文。如果你把RREQ和RREP也计入丢包率会异常高论文里无法解释。4.2 吞吐量的计算按时间窗口滑窗平均吞吐量不是“收到包总数除以总时长”这么单纯因为在移动场景中网络可能在前期没有建立完整路由。我常用滑窗法function avgThroughput computeThroughput(timeVec, byteCountVec, windowSize) avgThroughput zeros(size(timeVec)); halfW windowSize / 2; for i 1:length(timeVec) t0 timeVec(i) - halfW; t1 timeVec(i) halfW; mask timeVec t0 timeVec t1; sumBytes sum(byteCountVec(mask)); avgThroughput(i) sumBytes / sum(mask); % 字节每时隙 end avgThroughput avgThroughput * 8 / dt; % 转成bps end参数说明windowSize是滑窗宽度我建议设为5个仿真时隙。dt从主循环传入。如果你用1000字节的包吞吐量通常以kbps为单位这个量级和常见的MANET论文一致。4.3 参数表一组能够复现的仿真配置下表是我常用的AODV仿真默认配置适合作为对比实验的基线。你需要把这份配置写在代码的配置区而不是散落在主循环里。参数名称值说明areaSize1000x1000 m仿真区域numNodes50节点数range250 m最大通信距离packetSize1000 bytes数据包大小dataRate4 packet/s每流发送速率numFlows10并发业务流数simulationTime200 s仿真时长helloInterval1 sHello发送周期nodeSpeed0-10 m/sRandom Waypoint速度范围这些参数里面numFlows对结果影响最大。10条流会让网络负载偏高numFlows5时AODV的路由发现开销占比只有10%numFlows15时可能超过30%。如果你想展示路由开销对整体性能的影响把numFlows从5调到20绘制路由字节数曲线更有说服力。4.4 端到端时延的异常值处理统计时延的时候一定会有个别包因为重传等导致时延巨大。直接用mean会被离群值带偏。更靠谱的做法是取中位数或者先剔除超过2倍方差的点。可以用一行代码过滤delays stats.packetDelayArray; delaysClean delays(delays prctile(delays, 95)); medianDelay median(delaysClean);参数说明prctile(delays, 95)是为了去掉尾部5%的慢包。这些慢包往往是因为链路断裂触发多次路由发现时延可能比正常高一个数量级。保留它们会让你的柱状图显得异常高去掉它们并注明“P95截断”是常见做法。5. 三个容易踩的坑以及自己验证正确性的方法5.1 路由表生命周期没有刷新很多Matlab版本直接用“固定时隙”判断路由过期而不考虑MAC层实际传输时间。AODV规定每次通过该路由发送数据包都会刷新lifetime。如果你的代码里只写routeTable(entry).lifetime now timeout却没有在转发数据包后调用这个赋值那么稳定路径也会被误判超时导致不必要的路由重建。检查方法打印活跃路由的更新时间戳观察是否有“刚发完包就失效”的条目。5.2 广播与单播的传输范围不一致我用最小场景时犯过一个错RREQ广播时把所有邻居都当作可以收到包的节点但数据包发送时却是按照距离判定是否可达。这两个判定标准必须完全复用同一个函数。正确的做法是把isNeighborReachable作为唯一判定入口不管什么包都先过这个函数再做下一步。否则会出现RREQ“能广播到”但数据包“发不过去”的矛盾结果里表现为时延骤增、丢包率不稳定。5.3 随机性与可复现性的抉择MANET仿真结果有随机性这是它的天然特性。但如果你同时让节点初始位置、移动目标、流量源、目的都随机那么两次运行的结果差异会让你怀疑自己改错了代码。建议的做法是在仿真开始前固定随机流rng(42); % 固定全局随机数种子 nodes createAndDeployNodes([1000, 1000], 50);参数说明42可以换成任意整数为了方便对比每个实验用一组不同的种子比如1、2、3、4、5。每组种子跑一次然后取平均值和标准差。这比单次运行更有说服力也能够验证你的协议改动是不是稳定有效。写报告时把种子列表附在附录里别人复现时只需要把这行种子值改成对应列表即可。如果你发现固定种子后不同仿真时间点的吞吐量曲线仍然剧烈抖动那说明流量负载过高已经处于拥塞状态。此时应该降低dataRate而不是判定代码出错。验证AODV实现的正确性有一个低成本的办法把移动模型改成固定网格静止拓扑。如果AODV实现正确那么路由发现只需一次之后所有包都走同一跳路由端到端时延应该非常平稳。如果静止拓扑下丢包率仍然大于零基本可以断定是你的链路层距离判定有误而不是协议逻辑有问题。先把静止场景调通再去叠加移动模型这是我在每个Matlab AODV代码里都会执行的检查流程。本文还有配套的精品资源点击获取