简介:本资源是一套面向无线传感器网络(WSN)研究与教学的MATLAB能耗仿真源码,适用于通信工程、物联网及嵌入式系统方向的本科生、研究生及科研工程师,聚焦WSN中能量受限场景下的建模、分析与优化问题。压缩包共9个文件,含6个核心MATLAB函数(如LEACH路由实现、能耗计算、拓扑生成与动态可视化脚本)、2个预置数据文件(.mat格式用于初始化网络参数与协议配置)、1个动态演示GIF,整体大小为5.07MB;其中Runme.m为主入口,wsn_energy_con.m与router_leach.m分别实现节点级能耗建模与经典分簇路由逻辑,wsn_plot_an.m支持多维度能耗曲线与热力图输出。目前已有166人学习下载,提供从网络部署、通信建模、协议仿真到结果可视化的完整闭环流程,可直接运行复现LEACH等典型协议的能耗演化过程,辅助理解休眠策略、传输功率调控及路由负载均衡对网络生命周期的影响。
1. 项目概述与核心价值
最近在整理过往的项目资料,翻到了一个关于无线传感网络能耗仿真的老项目,用的是MATLAB。当时做这个的初衷,是想搞清楚在特定部署策略下,整个网络的生存周期到底受哪些因素影响最大。WSN(无线传感网络)这玩意儿,听起来高大上,其实核心就俩字:省电。节点通常靠电池供电,扔在野外或者不方便维护的地方,电用完了,网络就“死”了。所以,能耗仿真不是简单的算个功耗值,而是模拟整个网络从生到死的动态过程,评估路由协议、拓扑控制、节点部署这些策略到底靠不靠谱。
这个源码项目,就是搭建了这么一个仿真环境。它不依赖任何昂贵的硬件测试床,纯粹在MATLAB里,通过建模和离散事件仿真,把节点通信、数据处理、能量消耗这些抽象过程给量化出来。对于学生做课题、工程师前期验证算法性能,或者研究者快速对比不同方案的优劣,都是一个非常实用的工具。你能直观地看到,换一种路由算法,整个网络的“死亡地图”是怎么变化的;调整一下发射功率,第一个节点“饿死”的时间能推迟多少。这种可视化的洞察,比读一堆论文里的曲线图要直接得多。
接下来,我会把这个仿真项目的里里外外拆解清楚。从整体设计思路、核心能耗模型怎么建,到MATLAB源码的关键模块如何实现,最后再分享一些我调试时踩过的坑和性能优化的技巧。即使你之前没怎么接触过WSN仿真,跟着走一遍,也能自己搭个框架出来跑起来。
2. 仿真系统整体设计与建模思路
做仿真,最怕一开始思路不清,代码写着写着就成了一团乱麻。这个项目的核心目标是评估不同网络操作对节点能量消耗的影响,并预测网络生命周期。因此,整个设计是围绕“事件驱动”和“能量状态”这两个核心概念展开的。
2.1 核心模型抽象
首先,我们需要把真实的物理世界抽象成计算机能处理的模型。主要包含以下几层:
- 网络拓扑模型:决定节点在哪里、怎么连。我们常用的是随机均匀部署,节点撒在一个矩形区域内。源码里通过
rand()函数生成节点的 (x, y) 坐标。连通性通常基于距离判断,如果两节点距离小于通信半径R,则认为它们可以直连。这个模型虽然简单,但足以评估大多数基于距离的路由或分簇算法。 - 能耗模型:这是心脏。业界最常用的是Heinzelman模型(也就是LEACH协议里用的那个)。它把节点能耗分为三块:
- 发射能耗:发送
k比特数据到距离d外的节点。E_Tx(k, d) = k * E_elec + k * ε_fs * d^2(当 d < d0)E_Tx(k, d) = k * E_elec + k * ε_mp * d^4(当 d >= d0) 其中,E_elec是发射电路消耗的基带能量(每比特),ε_fs和ε_mp分别是自由空间和多径衰落模型的放大器系数,d0是一个距离阈值。这个公式意味着,短距离时能耗与距离平方成正比,长距离时与四次方成正比,远距离通信极其耗电。 - 接收能耗:接收
k比特数据。E_Rx(k) = k * E_elec - 空闲监听与睡眠能耗:为了简化,很多仿真将空闲状态能耗等同于接收能耗,睡眠状态能耗设为一个极小的值。更精细的模型会区分这几个状态。 在源码中,这些参数(如
E_elec=50nJ/bit,ε_fs=10pJ/bit/m^2,d0=87m)会被定义为全局常量。每个节点都有一个能量属性,每次通信后就从这里扣除。
- 发射能耗:发送
- 通信与流量模型:定义数据怎么产生、怎么传。通常采用周期性数据采集模型,每个节点每隔一定时间(如1秒)产生一个固定大小的数据包。路由模型则是关键变量,可以是简单的直接传输(到基站)、多跳最短路径(如Dijkstra算法)、或者分簇路由(如LEACH)。我们的源码会实现其中一两种作为基础,并预留接口方便替换。
- 事件驱动仿真引擎:这是仿真的大脑。我们不使用等步长的时间片推进(那样效率低),而是用事件队列。事件可以是:“Node_5在t=10.2s时产生一个数据包”、“Node_3在t=10.5s时收到来自Node_5的包并开始转发”。仿真器按事件发生的时间顺序处理,处理完一个事件后,可能又会生成新的未来事件(如转发包的下一个跳接收事件)插入队列。这种方法能跳过漫长的空闲期,效率极高。MATLAB中可以用优先队列或简单数组排序来实现。
2.2 仿真流程架构
基于以上模型,一个仿真回合(Round)的流程可以这样设计:
初始化:
- 设置区域大小、节点数量、初始能量、通信半径等参数。
- 随机部署节点,生成初始拓扑。
- 初始化全局事件列表,插入所有节点的第一个“数据生成事件”。
- 为每个节点创建数据结构,记录其位置、剩余能量、邻居列表、状态等。
主循环(事件处理):
- 从事件队列中取出下一个最早发生的事件。
- 检查当前仿真时间是否超过预设最大时间,或所有节点能量耗尽。若是,则结束仿真。
- 根据事件类型调用相应的处理函数:
- 处理数据生成事件:为源节点创建数据包,根据路由算法确定下一跳节点。计算发送该包所需的能耗,如果节点能量充足,则扣除能量,并创建一个“数据包发送完成事件”;如果能量不足,则标记该节点死亡,移除其相关未来事件。
- 处理数据包接收事件:检查接收节点是否存活且能量充足。计算接收能耗并扣除能量。如果该节点是目的节点(如基站),则记录数据。否则,继续根据路由算法转发(即创建新的发送事件)。
- 更新仿真时间到当前事件完成的时间。
- 循环。
数据收集与可视化:
- 实时或每轮结束后记录:存活节点数、网络总剩余能量、已接收的数据包总数、第一个节点死亡时间(FND)、一半节点死亡时间(HND)、最后一个节点死亡时间(LND)等。
- 使用MATLAB的绘图函数,动态或静态展示网络拓扑变化、能量分布热力图、生命周期曲线等。
注意:在建模时,一个常见的简化是忽略信号冲突和重传(MAC层影响)。这对于初步评估路由层算法是可行的。但如果要研究更底层的影响,就需要引入更复杂的MAC模型,比如CSMA/CA,那会大大增加事件复杂度和仿真时间。
3. MATLAB源码核心模块解析
光讲理论不行,我们直接看代码怎么组织。一个结构清晰的仿真程序通常分为几个模块化的脚本或函数文件。下面我结合关键代码片段(为简洁,已做简化)来解释。
3.1 参数定义与初始化模块 (init_simulation.m)
这个文件定义了整个仿真的“宪法”。所有可调的参数都集中在这里。
% 网络参数 area_x = 100; % 区域长度 (m) area_y = 100; % 区域宽度 (m) num_nodes = 100; % 节点总数 sink_x = 50; % 基站/汇聚节点x坐标 sink_y = 50; % 基站/汇聚节点y坐标 % 能耗模型参数 (基于经典无线电模型) E_elec = 50e-9; % 50 nJ/bit, 发射或接收电路能耗系数 E_fs = 10e-12; % 10 pJ/bit/m^2, 自由空间放大器系数 E_mp = 0.0013e-12; % 0.0013 pJ/bit/m^4, 多径衰落放大器系数 d0 = sqrt(E_fs / E_mp); % 距离阈值,约87米 packet_size = 4000; % 数据包大小,单位比特 (bit) % 节点初始能量 E_init = 0.5; % 每个节点初始能量,单位焦耳 (J) % 通信参数 R = 15; % 节点通信半径 (m) data_generation_interval = 1; % 每个节点生成数据包的间隔 (秒) % 仿真控制参数 max_simulation_time = 500; % 最大仿真时间 (秒)初始化节点属性通常用一个结构体数组:
for i = 1:num_nodes nodes(i).id = i; nodes(i).x = rand * area_x; nodes(i).y = rand * area_y; nodes(i).energy = E_init; nodes(i).is_alive = true; nodes(i).neighbors = []; % 邻居列表,后续计算 nodes(i).packets_sent = 0; nodes(i).packets_relayed = 0; end % 单独设置汇聚节点(通常编号为num_nodes+1或固定ID) sink.id = num_nodes + 1; sink.x = sink_x; sink.y = sink_y;3.2 邻居发现与路由计算模块 (build_topology.m,calculate_route.m)
拓扑构建就是根据通信半径R计算每个节点的邻居。
function nodes = build_topology(nodes, R, sink) num_nodes = length(nodes); for i = 1:num_nodes neighbors_i = []; for j = 1:num_nodes if i ~= j d = sqrt((nodes(i).x - nodes(j).x)^2 + (nodes(i).y - nodes(j).y)^2); if d <= R neighbors_i = [neighbors_i, j]; end end end % 检查是否能直接连接到汇聚节点 d_to_sink = sqrt((nodes(i).x - sink.x)^2 + (nodes(i).y - sink.y)^2); if d_to_sink <= R nodes(i).can_direct_to_sink = true; else nodes(i).can_direct_to_sink = false; end nodes(i).neighbors = neighbors_i; end end路由算法是核心变量。我们以实现一个简单的最短跳数路由为例。这本质上是一个在由节点和邻居关系构成的图上,寻找源节点到汇聚节点的最短路径问题。可以使用广度优先搜索(BFS)。
function path = shortest_path_bfs(source_id, nodes, sink) % 返回从源节点到汇聚节点的节点ID路径 num_nodes = length(nodes); visited = false(1, num_nodes+1); % 包含sink parent = zeros(1, num_nodes+1); queue = source_id; visited(source_id) = true; while ~isempty(queue) current = queue(1); queue(1) = []; % 如果当前节点是sink(虚拟节点),回溯路径 if current == sink.id path = []; while current ~= source_id path = [current, path]; current = parent(current); end path = [source_id, path]; return; end % 获取当前节点的邻居(真实节点) if current <= num_nodes neighbors = nodes(current).neighbors; else neighbors = []; % sink没有邻居列表,特殊处理 end % 检查是否能直达sink(视为邻居) if current <= num_nodes && nodes(current).can_direct_to_sink neighbors = [neighbors, sink.id]; end for nid = neighbors if ~visited(nid) visited(nid) = true; parent(nid) = current; queue = [queue, nid]; end end end % 如果没有找到路径 path = []; end3.3 能耗计算模块 (energy_consumption.m)
这个函数根据距离和数据包大小,精确计算一次通信的能耗。
function [E_used, is_alive] = calculate_tx_energy(node, distance, packet_size, E_elec, E_fs, E_mp, d0) % 计算发送能耗,并检查节点是否存活 if distance < d0 E_used = packet_size * E_elec + packet_size * E_fs * distance^2; else E_used = packet_size * E_elec + packet_size * E_mp * distance^4; end if node.energy >= E_used is_alive = true; else is_alive = false; E_used = node.energy; % 耗尽所有剩余能量 end end function E_used = calculate_rx_energy(packet_size, E_elec) % 计算接收能耗 E_used = packet_size * E_elec; end3.4 主仿真循环与事件调度 (main_simulation.m)
这是最复杂的部分,我们实现一个简化版的事件循环。事件可以用结构体表示:event.type,event.time,event.node_id,event.packet等。
% 初始化事件列表:每个节点在随机时间生成第一个包 events = []; % 将用数组模拟优先队列,按时间排序 for i = 1:num_nodes event.time = rand * data_generation_interval; % 错开初始时间 event.type = 'generate'; event.node_id = i; events = [events; event]; end % 按时间排序 [~, idx] = sort([events.time]); events = events(idx); current_time = 0; alive_nodes = num_nodes; stats = []; % 用于记录统计信息 while ~isempty(events) && current_time < max_simulation_time && alive_nodes > 0 % 取出最早事件 current_event = events(1); events(1) = []; current_time = current_event.time; node_id = current_event.node_id; node = nodes(node_id); if ~node.is_alive continue; % 节点已死亡,忽略其事件 end switch current_event.type case 'generate' % 1. 为该节点创建数据包 packet.src = node_id; packet.seq = nodes(node_id).packets_sent + 1; packet.size = packet_size; % 2. 计算路由路径 path = shortest_path_bfs(node_id, nodes, sink); if isempty(path) % 无路径,丢包 fprintf('Node %d: No path to sink at time %.2f\n', node_id, current_time); else % 3. 发送到下一跳 next_hop = path(2); % path(1)是自己 distance = calculate_distance(node, nodes, sink, next_hop); [E_used, is_alive] = calculate_tx_energy(node, distance, packet_size, E_elec, E_fs, E_mp, d0); if is_alive nodes(node_id).energy = nodes(node_id).energy - E_used; nodes(node_id).packets_sent = nodes(node_id).packets_sent + 1; % 4. 创建接收事件(考虑传输延迟,这里简化为固定小延迟) new_event.time = current_time + 0.001; % 1ms传输延迟 new_event.type = 'receive'; new_event.node_id = next_hop; new_event.packet = packet; events = add_event_sorted(events, new_event); else nodes(node_id).is_alive = false; alive_nodes = alive_nodes - 1; fprintf('Node %d died during transmission at time %.2f\n', node_id, current_time); end end % 5. 安排下一个生成事件 new_event.time = current_time + data_generation_interval; new_event.type = 'generate'; new_event.node_id = node_id; events = add_event_sorted(events, new_event); case 'receive' % 1. 计算接收能耗 E_used = calculate_rx_energy(packet_size, E_elec); if nodes(node_id).energy >= E_used nodes(node_id).energy = nodes(node_id).energy - E_used; nodes(node_id).packets_relayed = nodes(node_id).packets_relayed + 1; % 2. 判断是否是最终目的地(汇聚节点) if node_id == sink.id % 数据到达汇聚节点,记录成功传输 stats.packets_delivered = stats.packets_delivered + 1; else % 3. 继续转发 % ... (类似generate中的转发逻辑,寻找下一跳,计算发送能耗,创建新的接收事件) end else nodes(node_id).is_alive = false; alive_nodes = alive_nodes - 1; fprintf('Node %d died during reception at time %.2f\n', node_id, current_time); end end % 定期记录统计信息(例如每10秒) if mod(floor(current_time), 10) == 0 record_stats(stats, current_time, alive_nodes, nodes); end % 更新节点状态(可选,用于可视化) nodes = update_node_status(nodes); endadd_event_sorted是一个辅助函数,用于将新事件插入到已排序的事件列表中,保持时间顺序。
3.5 结果可视化模块 (plot_results.m)
仿真的结果需要直观展示。常用的图包括:
- 网络拓扑演化图:用
scatter绘制节点位置,用颜色或大小表示剩余能量,用线条表示数据流。 - 网络生命周期曲线:绘制存活节点数随时间变化的曲线。可以清晰标出FND, HND, LND。
- 能量消耗分布图:仿真结束时,用柱状图或另一个散点图展示每个节点的总能耗或剩余能量,找出“热点”节点。
- 数据包投递率曲线:展示成功传送到汇聚节点的数据包比例随时间的变化。
function plot_network_lifetime(stats) figure('Position', [100, 100, 800, 600]); subplot(2,2,1); plot(stats.time_vector, stats.alive_nodes_vector, 'b-', 'LineWidth', 2); xlabel('仿真时间 (秒)'); ylabel('存活节点数'); title('网络生命周期曲线'); grid on; hold on; % 标记FND, HND等 [fnd_time, fnd_idx] = find_first_node_death(stats); plot([fnd_time, fnd_time], [0, max(stats.alive_nodes_vector)], 'r--'); text(fnd_time, max(stats.alive_nodes_vector)*0.9, sprintf('FND: %.1fs', fnd_time)); subplot(2,2,2); scatter([nodes.x], [nodes.y], 50, [nodes.energy], 'filled'); colorbar; xlabel('X坐标 (m)'); ylabel('Y坐标 (m)'); title('节点最终能量分布'); axis([0 area_x 0 area_y]); subplot(2,2,3); bar(1:num_nodes, [nodes.packets_relayed]); xlabel('节点ID'); ylabel('转发数据包数'); title('节点负载分布'); grid on; subplot(2,2,4); plot(stats.time_vector, stats.packets_delivered_ratio_vector, 'g-', 'LineWidth', 2); xlabel('仿真时间 (秒)'); ylabel('数据包投递率'); title('投递率随时间变化'); grid on; end4. 仿真实现中的关键步骤与参数调优
有了框架,要让仿真跑得既准确又高效,还需要注意很多细节。这里分享几个关键步骤的实现技巧和参数调优经验。
4.1 事件队列的高效实现
前面用数组排序来实现事件队列,在事件量不大时没问题。但当节点多、仿真时间长时,频繁排序(O(n log n))会成为瓶颈。更高效的方法是使用最小堆(优先队列)。MATLAB没有内置的堆数据结构,但可以自己实现,或者利用containers.Map配合自定义排序键来模拟。一个更简单实用的优化是使用“时间桶”:
- 将时间轴划分为固定长度的桶(例如每0.1秒一个桶)。
- 每个桶里存放该时间段内发生的事件。
- 处理事件时,按时间顺序遍历桶,桶内的事件无需严格排序。
- 这种方法牺牲了一点精度(事件顺序在桶内可能乱序),但大大提高了插入和删除的效率,特别适合对事件时间精度要求不极端的场景。
4.2 能耗模型参数的校准
能耗模型中的参数(E_elec,E_fs,E_mp)直接决定了仿真结果的绝对数值。这些值需要参考真实传感器节点的芯片手册(如CC2530, nRF52840)。不同芯片、不同工作电压、不同数据速率下,这些值差异很大。
E_elec:通常由芯片的发射/接收电流和工作电压、比特率计算得出。例如,某芯片发射模式电流20mA,电压3V,比特率250kbps,则发送1比特的时间是4微秒,能量是20mA * 3V * 4e-6 s = 240 nJ/bit。这比我们常用的50nJ/bit大很多。务必根据你仿真的目标硬件平台查找或估算这个值,否则生命周期预测会严重失准。- 距离阈值
d0:d0 = sqrt(E_fs / E_mp)是一个理论值。在实际环境中,这个阈值会受到天线高度、障碍物、地面反射等影响。在仿真中,可以将其作为一个可调参数,研究不同信道模型对路由选择的影响。
4.3 路由算法的集成与比较
仿真框架的价值在于能方便地替换和比较不同算法。除了最短跳数,常见的还有:
- LEACH(低功耗自适应集簇分层):需要实现分簇算法(每轮随机选择簇头)、簇内TDMA调度、簇头到基站的数据聚合与转发。
- GEAR(基于地理位置的能耗感知路由):节点需要知道自己的地理位置,选择到基站代价(结合距离和剩余能量)最小的邻居作为下一跳。
- AODV(按需距离矢量路由):这是一种反应式路由,需要实现路由请求(RREQ)洪泛、路由回复(RREP)和路由维护机制。仿真事件会复杂很多,包括控制包的处理。
在代码中,最好的做法是定义一个路由接口函数,例如next_hop = routing_function(src_id, current_id, nodes, sink)。这样,在主循环中只需调用这个函数,而无需关心内部实现。要比较算法时,只需切换这个函数。
4.4 仿真结果的统计显著性
无线信道和节点部署都是随机的,一次仿真的结果可能有偶然性。因此,必须进行多次独立重复实验。
- 蒙特卡洛仿真:将整个仿真流程(从节点随机部署开始)运行
N次(例如30次)。 - 收集关键指标:每次运行记录FND, HND, LND, 总收包数等。
- 计算统计量:对每个指标,计算其30次结果的平均值、标准差、置信区间(例如95%置信区间)。
- 绘制带误差棒的曲线:当比较两种算法时,应该绘制其平均生命周期曲线,并用阴影区域或误差棒表示其波动范围。这样才能科学地判断算法A是否显著优于算法B。
在MATLAB中,这意味著需要一个外层的for循环来控制实验次数,并妥善管理每次实验的随机数种子(例如rng(experiment_index))以确保可重复性。
5. 常见问题、调试技巧与性能优化实录
仿真程序写起来容易,调起来头疼。下面是我在开发过程中遇到的一些典型问题及解决方法。
5.1 仿真结果不稳定或不可重复
- 问题:每次运行,节点死亡顺序、网络生命周期差异很大。
- 原因:没有控制随机数种子。MATLAB的
rand、randi等函数默认基于一个随时间变化的种子。 - 解决:在每次实验开始时,使用
rng(seed)固定随机数种子。例如,rng(1)总是产生相同的随机序列。这对于调试和对比实验至关重要。在最终进行蒙特卡洛实验时,可以使用循环for seed = 1:30; rng(seed); ... end。
5.2 仿真速度过慢
- 问题:节点数增加到200以上,仿真时间指数级增长,跑一次要几个小时。
- 原因与排查:
- 邻居发现:最开始的
build_topology函数用了双重循环,复杂度是O(N^2)。100个节点是1万次计算,200个节点就是4万次,400个节点是16万次。这是第一个瓶颈。 - 事件队列操作:使用数组排序,每次插入事件都是
O(n log n)。事件数量庞大时极慢。 - 路由计算:每次发送数据包都运行一次BFS或Dijkstra算法,复杂度
O(N+E)。在密集网络中,这开销巨大。
- 邻居发现:最开始的
- 优化方案:
- 邻居发现优化:使用空间划分数据结构,如网格法。将区域划分为边长为通信半径
R的网格。每个节点根据坐标放入对应网格。寻找邻居时,只需检查本网格和相邻8个网格中的节点。复杂度降至近似O(N)。 - 事件队列优化:如前所述,实现最小堆或使用时间桶法。
- 路由优化:对于静态路由(如最短跳数),可以在仿真开始前预计算所有节点到汇聚节点的路由表,存储在每个节点的属性里。发送数据时直接查表,
O(1)复杂度。对于动态路由(如LEACH每轮换簇头),可以每轮开始时计算一次,而不是每个包计算一次。 - 向量化操作:MATLAB擅长矩阵运算。例如,计算所有节点两两距离矩阵,可以用向量化操作代替循环,速度提升成百上千倍。但要注意内存消耗,节点太多时矩阵会非常大。
- 减少绘图频率:实时可视化是性能杀手。改为每100个事件或每10秒仿真时间更新一次图形,或者关闭图形句柄的自动更新 (
set(gcf, 'Visible', 'off')),最后再统一绘制。
- 邻居发现优化:使用空间划分数据结构,如网格法。将区域划分为边长为通信半径
5.3 能量耗尽后节点产生“幽灵事件”
- 问题:节点A在t=10s时能量耗尽死亡,但事件队列里还存有它在t=10.5s要处理的数据包接收事件。当仿真时间推进到10.5s时,程序试图让已死亡的节点A去接收数据,导致逻辑错误或崩溃。
- 解决:这是事件驱动仿真的经典问题。必须在处理每个事件的第一步,就检查事件所关联的节点是否仍然存活。如果节点已死亡,则直接丢弃该事件,不进行任何处理。如上文主循环代码中的
if ~node.is_alive; continue; end判断。
5.4 数据包在环路中无限转发
- 问题:在网络拓扑变化或路由算法有缺陷时,数据包可能在两个或多个节点间来回转发,永远到不了目的地,耗尽节点能量并塞满事件队列。
- 解决:
- TTL(生存时间):在每个数据包结构中增加一个
ttl字段,初始值设为网络直径(如节点数的估计值)。每转发一次,ttl减1。当ttl为0时,丢弃该包。 - 路径记录:在数据包中记录它经过的节点ID序列。如果发现下一个要转发的节点已经在历史路径中,则检测到环路,丢弃该包或触发路由修复。
- 路由算法健壮性:确保使用的路由算法(如AODV)本身具有环路避免机制。
- TTL(生存时间):在每个数据包结构中增加一个
5.5 结果与理论或论文差异大
- 问题:自己仿真的网络生命周期比参考文献中报告的短很多。
- 排查步骤:
- 检查参数:逐项对比能耗参数(
E_elec,E_fs)、数据包大小、数据生成间隔。这是最常见的原因。 - 检查流量模型:论文中可能假设了数据聚合(簇头将多个包聚合成一个),而你的仿真中每个包都独立发送。聚合能极大节省能量。
- 检查能耗状态:论文可能考虑了睡眠调度,节点大部分时间处于极低功耗的睡眠状态,而你只模拟了收发和空闲状态。加入睡眠调度能大幅延长生命周期。
- 检查信道模型:论文可能使用了更复杂的信道衰减模型或考虑了链路可靠性(丢包率),导致实际有效通信距离更短,多跳次数增多,能耗分布不同。
- 检查性能指标定义:确认你们对“网络死亡”的定义是否一致。是第一个节点死亡?还是基站与所有节点断开连接?或者是数据投递率低于某个阈值?
- 检查参数:逐项对比能耗参数(
为了快速定位问题,一个很好的习惯是先做一个极简场景的单元测试。例如,只部署两个节点和一个基站,手动推算它们应该在哪一刻死亡,然后运行仿真看结果是否匹配。从简单到复杂,逐步增加节点和功能,每步都验证。
本文还有配套的精品资源,点击获取