简介:无线传感器网络(WSN)由大量能量受限的节点组成,如何高效利用有限能量、延长网络生命周期是路由协议设计的核心挑战。分簇路由通过簇首数据融合有效降低传输能耗,但均匀分簇易导致Sink附近节点因过载而形成“热区效应”,造成能量空洞。针对这一问题,本文探讨一种非均匀分簇的WSN能量均衡路由协议设计思路:通过让簇大小随节点到Sink距离动态调整,并融合剩余能量感知的簇首选举与多跳中继选择策略,在降低通信开销的同时分散热点负载。结合Matlab仿真验证,该方案可显著推迟首个节点死亡时间,提升网络存活轮数。这类能量均衡算法在环境监测、智能农业等大规模无线传感器网络部署中具有重要的工程应用价值,也为相关路由优化研究提供了可复现的仿真实现参考。
1. 为什么做非均匀分簇:从WSN的能耗困境说起
无线传感器网络(WSN)的节点通常靠电池供电,部署在野外或者人类难以到达的区域,换电池几乎是不可能的事。所以,如何让整个网络的能量消耗得更均匀、让网络的生命周期尽可能延长,一直是WSN路由协议设计的核心命题。我在做这个项目之前,先花了不少时间梳理现有的分簇路由协议,搞清楚它们各自的优劣,才最终确定以非均匀分簇为突破口来设计路由方案。
1.1 平面路由与分簇路由的本质区别
早期WSN的路由协议大多是平面结构的,所有节点地位平等,数据通过多跳方式逐级转发到汇聚节点(Sink)。这种模式实现简单,但问题也很明显:靠近Sink的节点既要发送自己的数据,又要转发远处节点的数据,负载极大,能量消耗速度快。网络运行一段时间后,这些关键节点会率先耗尽能量,导致网络出现“能量空洞”,整个网络与Sink的连通性被切断。
分簇路由协议的出现缓解了这个问题。簇首节点负责收集簇内成员的数据,经过融合处理后直接或者通过其他簇首转发给Sink。这样一来,普通节点只需要把数据传给距离较近的簇首,传输距离大大缩短,能耗也因此降低。LEACH是这一类协议中最具代表性的经典方案,它通过随机轮换簇首来均衡节点能耗,但LEACH的问题在于簇首直接与Sink通信,远距离传输能耗太高,在大规模网络中并不实用。
1.2 均匀分簇的隐藏弊端:热区效应
很多基于分簇的改进协议仍然采用均匀分簇。所谓均匀分簇,就是每个簇的规模差不多、簇首数量在各区域分布也大致相同。可仔细想想,网络中的数据流是有方向性的——所有数据最终都要流向Sink。如果Sink位于网络区域的某个边缘或角落,那么靠近Sink的簇首不仅要转发自己簇内的数据,还要承担来自更远区域簇首的数据中继任务。这些靠近Sink的簇首长期处于高负载状态,能量衰减速度远超网络其他区域。
这就是WSN路由设计中著名的“热区效应”或者叫“Sink附近能量空洞问题”。我刚开始做这项研究时,第一版方案就是简单的均匀分簇加多跳路由,仿真结果很不理想。网络运行到大约60%生命周期时,Sink周围一圈的节点几乎全部死亡,而远端的节点还有40%左右的剩余能量。这种能量浪费非常可惜,也促使我去寻找更合理的路由方案。
1.3 借助Matlab仿真验证问题场景
在正式设计非均匀分簇协议之前,我习惯先用Matlab把基础场景搭建起来,方便直观观察热区效应的影响。我使用的网络参数如下:
| 参数项 | 数值 |
|---|---|
| 网络区域 | 200m × 200m |
| 节点数量 | 200 |
| Sink位置 | (100, 100),即区域中心 |
| 初始能量 | 0.5J/节点 |
| 发送电路功耗 | 50nJ/bit |
| 接收电路功耗 | 50nJ/bit |
| 自由空间功耗系数 | 10pJ/bit/m² |
| 多径衰减功耗系数 | 0.0013pJ/bit/m⁴ |
| 数据包大小 | 4000bit |
在这个场景下做了两轮测试:第一轮采用均匀分簇加单跳传输,第二轮采用均匀分簇加多跳传输。结果很能说明问题——单跳模式下远端节点能量消耗极快,整体网络生命周期短;多跳模式下网络生命周期有所提升,但Sink近邻节点最早成批死亡,热区效应非常明显。
基于这些仿真观察,我确认了一个判断:在分簇路由的设计中,不能把目光局限在分簇算法本身,还得关注簇间路由的负载情况。非均匀分簇的核心思想就是:离Sink越近的区域,簇的规模越小;离Sink越远的区域,簇的规模越大。这样近Sink区域的簇首数量更多,可以把数据转发任务分摊到更多节点上,避免少数节点承担过多中继任务而过早耗尽能量。
2. 能量均衡方案的设计思路与实现路径
确定了非均匀分簇这个大方向之后,接下来要面对的是两个核心问题:簇首如何选取?簇间如何路由?这两个问题环环相扣,直接决定了协议的能量均衡效果。
2.1 竞争半径的确定:让簇大小与距离挂钩
非均匀分簇的第一步是给每个节点分配合适的竞争半径。竞争半径决定了节点在竞选簇首时的覆盖范围,半径越小,形成的簇也越小。为了让近Sink区域的簇更小、远Sink区域的簇更大,我需要设计一个与节点到Sink距离相关的竞争半径函数。
我参考了EEUC(Energy-Efficient Uneven Clustering)协议的思想,并结合仿真场景做了调整。竞争半径的计算公式可以表示如下:
% 计算节点到Sink的距离 distToSink = sqrt((nodes(i).x - sink.x)^2 + (nodes(i).y - sink.y)^2); dmax = sqrt(2) * fieldSize; % 网络对角线长度作为最大距离参考 % 非均匀竞争半径计算 Rmax = 90; % 最大竞争半径 c = 0.5; % 非均匀程度控制系数 Rcomp = Rmax * (1 - c * (distToSink / dmax));这个公式的含义很直观:距离Sink越近的节点,Rcomp值越小,竞选簇首时覆盖的范围越小,形成的簇就越小;距离Sink越远的节点,竞争半径越大,可以覆盖更多成员节点,形成更大的簇。c系数调节非均匀的程度,c越大,近远区域的簇大小差异越明显。
实际调试时,我发现c取值需要根据网络规模和Sink位置动态调整。如果c取值过大,远端簇太大,簇首融合和传输负担过重;如果c取值过小,近Sink区域的簇没有足够小,热区效应又回来了。我最终的测试结果显示,在200m×200m、200个节点的场景下,c取0.4~0.6比较合适。
2.2 簇首选举:能量优先与随机竞争的结合
有了竞争半径,接下来就是簇首选举。一昧地根据剩余能量选簇首,会造成某些高能量节点反复当选,反而加快这些节点的能量消耗。更合理的做法是让节点在剩余能量满足一定门槛的前提下,通过随机延迟竞争机制自动推选出簇首。
我的实现思路是这样的:
- 每轮开始时,每个节点根据自己的剩余能量计算一个竞选延迟时间:
% 节点竞选延迟时间计算 Eavg = mean([nodes.energy]); % 网络平均剩余能量 Eresidual = nodes(i).energy; % 延迟时间与剩余能量成反比,能量高者延迟短 Tdelay = Tmax * (1 - Eresidual / Eavg) * rand(1);Eresidual/Eavg比值越大,节点延迟时间越短,意味着高能量节点更早广播竞选消息。
节点在延迟时间内持续监听信道。如果收到其他节点广播的簇首竞选消息,而且该节点距离在自身竞争半径之内,节点自动放弃竞选,转为普通成员节点。如果延迟时间结束仍没有收到比自己更“强势”的竞选者,节点正式宣布成为簇首。
簇首选举完成后,普通节点选择加入哪个簇:优先选择距离最近且通信代价最小的簇首。这里我用了一个简单的代价函数,综合考虑距离和簇首剩余能量:
% 节点选择簇首的代价函数 cost = distToCH^2 / (nodes(CH).energy + eps);选择代价最小的簇首加入,这样可以在一定程度上避免低能量簇首因接纳过多成员而过早死亡。
2.3 簇间多跳路由:避开低能量节点
簇形成后,数据需要从远端簇首逐级转发到Sink。这一步如果处理不好,前面非均匀分簇的努力就白费了。簇间多跳路由的难点在于:选择哪一跳作为中继节点,才能让整条路径的能量消耗最小、各节点的负载相对均衡?
我采用了一种基于“最小化能耗与残余能量”的启发式选路策略。每个簇首在构建路由时,只需要在自己到Sink的“前半区域”寻找候选中继节点:
% 簇间路由选择 for j = 1:numCH % 候选中继:比自己更靠近Sink的簇首 if distToSink(CH(j)) < distToSink(CH(i)) % 计算中继代价 relayCost(j) = dist(CH(i), CH(j))^2 + dist(CH(j), sink)^2; relayCost(j) = relayCost(j) / (CH(j).energy + eps); end end % 选择代价最小的中继 nextHop = find(relayCost == min(relayCost));这个代价函数包含两项考量:一是路径的通信距离,距离越短,通信能耗越低;二是中继节点的剩余能量,剩余能量越多,被选中作为中继的优先级越高。两者结合,既能减少不必要的能量开销,又能避免低能量节点被频繁选中而加速死亡。
2.4 协议运行流程总览
整个协议的运行流程可以整理为以下步骤:
- 网络初始化,节点随机部署,Sink广播自己的位置信息。
- 每轮开始时,节点根据当前剩余能量和到Sink距离计算竞争半径。
- 节点基于剩余能量计算竞选延迟时间,竞争产生本轮簇首。
- 普通节点根据代价函数选择加入的簇,簇内成员向簇首发送数据。
- 簇首收到成员数据后执行融合,然后根据簇间路由策略选择中继节点。
- 数据经过多跳转发最终到达Sink。
- 一轮数据采集完成后,如果节点能量仍有剩余,继续下一轮;否则节点死亡网络终止。
3. Matlab仿真的完整实现细节
3.1 仿真框架与主程序结构
Matlab是WSN路由算法验证的常用工具,代码编写效率高,适合快速迭代验证算法可行性。我的仿真主程序并没有用很复杂的工具箱,纯粹靠脚本逻辑模拟网络运行过程,方便二次开发和参数调整。
主程序核心结构如下:
% WSN非均匀分簇路由协议主程序 clc; clear; close all; % 参数初始化 fieldSize = 200; % 网络区域边长 numNodes = 200; % 节点数量 sinkPos = [100, 100]; % Sink位置 Einit = 0.5; % 节点初始能量 roundMax = 5000; % 最大轮数 packetLen = 4000; % 数据包长度 % 节点初始化 for i = 1:numNodes nodes(i).x = rand * fieldSize; nodes(i).y = rand * fieldSize; nodes(i).energy = Einit; nodes(i).type = 'N'; % N=普通节点, C=簇首 nodes(i).clusterHead = 0; nodes(i).distToSink = sqrt((nodes(i).x - sinkPos(1))^2 + ... (nodes(i).y - sinkPos(2))^2); end % 主循环 for r = 1:roundMax % 1. 检查节点存活情况 aliveNodes = find([nodes.energy] > 0); if length(aliveNodes) < 5 break; % 存活节点数不足,结束仿真 end % 2. 簇首选举(非均匀竞争) [nodes, CHSet] = clusterHeadSelection(nodes, sinkPos, fieldSize); % 3. 成员节点入簇 nodes = memberJoin(nodes, CHSet); % 4. 簇间多跳路由构建 [nodes, routingTable] = interClusterRouting(nodes, CHSet, sinkPos); % 5. 数据传输与能量更新 nodes = dataTransmission(nodes, CHSet, routingTable, packetLen); % 6. 记录本轮数据 record(r) = length(aliveNodes); end3.2 能耗模型的建模与参数说明
能耗模型的准确性直接决定了仿真结果的可信度。我采用目前WSN路由协议文献中应用最广泛的一阶无线通信模型:
发送l bit数据,传输距离为d时的能耗为:
- 当 d < d0(阈值距离)时,采用自由空间模型:Etx = l * Eelec + l * eps_fs * d²
- 当 d ≥ d0 时,采用多径衰减模型:Etx = l * Eelec + l * eps_mp * d⁴
接收l bit数据的能耗为:Erx = l * Eelec
数据融合的能耗为:Eda = l * EDA
其中,阈值距离d0 = sqrt(eps_fs / eps_mp)。这个d0的意义在于判断信号在不同距离下的衰减特性:短距离传输时信号衰减相对平缓,按距离平方计算能耗;长距离传输时信号衰减加剧,按距离四次方计算能耗。
很多初学者容易忽略一个问题:d0的数值由两个功率放大系数决定。如果随意设置这两个系数,可能导致d0过大或过小,从而影响整个网络通信模式的选择。我的仿真参数中,d0的值约为87.7米,这说明在大多数簇内通信场景(通常簇成员到簇首距离不超过50米)下,能耗以d²为主;而簇首到Sink的远程传输,如果距离超过87.7米则需要采用d⁴模型。
3.3 分簇算法的Matlab代码实现
簇首选举是非均匀分簇协议的核心环节,我在这里把关键代码贴出来,并配上设计意图说明:
function [nodes, CHSet] = clusterHeadSelection(nodes, sinkPos, fieldSize) numNodes = length(nodes); Rmax = 90; dmax = sqrt(2) * fieldSize; c = 0.5; Tmax = 1.0; % 最大竞选延迟时间 % 先计算各节点的竞争半径和剩余能量比 for i = 1:numNodes if nodes(i).energy <= 0 continue; end distToSink = nodes(i).distToSink; nodes(i).Rcomp = Rmax * (1 - c * (distToSink / dmax)); nodes(i).delayTime = Tmax * (1 - nodes(i).energy / mean([nodes.energy])) * rand; nodes(i).isCH = 0; end % 按延迟时间排序,时间短的先参与竞选 [~, order] = sort([nodes.delayTime]); CHSet = []; for idx = 1:numNodes i = order(idx); if nodes(i).energy <= 0 continue; end % 判断是否收到过其他簇首的竞选消息 if nodes(i).isCH == 1 continue; end % 成为簇首的条件:延迟时间结束,且竞争半径内没有其他簇首 canBeCH = true; for k = 1:length(CHSet) dist = sqrt((nodes(i).x - nodes(CHSet(k)).x)^2 + ... (nodes(i).y - nodes(CHSet(k)).y)^2); if dist < nodes(i).Rcomp canBeCH = false; break; end end if canBeCH nodes(i).isCH = 1; nodes(i).type = 'C'; CHSet = [CHSet, i]; end end end这段代码执行后,网络中的节点会自动形成规模不等的簇:近Sink区域的节点竞争半径小,簇首密度高;远Sink区域的节点竞争半径大,簇首相对稀疏,每个簇覆盖的面积更大、成员更多。
3.4 数据分析与可视化
仿真结束后,我通常会绘制三张核心图表来评估协议性能:
第一张是存活节点数随时间变化曲线,这张图可以直观展示网络生命周期。曲线越平缓、下降越晚,说明能量均衡效果越好。
第二张是节点剩余能量分布图,我通常会在网络运行到50%节点死亡时,把每个节点的剩余能量画成热力图。这张图可以用来检验是否存在热区效应:如果Sink附近节点剩余能量明显低于远程区域,说明协议没有实现预期的能量均衡。
第三张是不同协议的生命周期对比图,我把LEACH、EEUC和本文协议放在同一条件下运行,统计首个节点死亡轮数(FND)和半数节点死亡轮数(HND),汇总成表格做对比分析。
4. 关键参数调试与对比实验结果
4.1 非均匀程度系数c的调节经验
c参数的调节是整个协议设计中最需要耐心的一环。我做了一组对比实验,在相同场景下分别令c=0(完全均匀分簇)、c=0.3、c=0.5、c=0.7,观察首节点死亡轮数变化:
| 非均匀系数c | 首个节点死亡轮数 | 半数节点死亡轮数 | 网络运行总轮数 |
|---|---|---|---|
| 0(均匀) | 1376 | 2658 | 3925 |
| 0.3 | 1552 | 2850 | 4180 |
| 0.5 | 1710 | 3012 | 4470 |
| 0.7 | 1645 | 2888 | 4302 |
从数据中可以看出:c从0增大到0.5时,首节点死亡时间明显延后,说明非均匀分簇确实缓解了热区效应;但c增大到0.7时,网络生命周期反而下降,原因是远端簇过大,簇首需要融合和处理的数据量过多,簇首自身能耗大幅上升,导致部分节点提前死亡。
这组实验告诉我们一个道理:非均匀分簇不是越“非均匀”越好,而是在“近Sink区域负载分摊”和“远端簇首数据负载”之间寻找平衡点。具体场景的最优c值,必须通过仿真实验来确定。
4.2 选举机制中Tmax的影响
Tmax是竞选延迟时间的尺度参数。Tmax过小,节点竞选消息可能在网络中传播不充分就结束了,导致簇首竞争不充分、选举结果偏随机;Tmax过大,选举阶段占用时间过长,导致每轮数据采集效率降低。
我在仿真测试中发现,只要Tmax设定在0.5~2.0秒之间,对最终网络生命周期的影响并不显著,主要影响的是运行速度。更大的问题是Tmax和随机数rand()结合后,会引入一定随机性,导致同样参数下多次仿真结果有波动。解决办法是把同一配置的仿真重复运行5次,取平均结果作为最终指标。
4.3 与LEACH协议的性能对比
我选取了LEACH和EEUC两个经典协议作为对比基准,在完全相同的网络场景下运行仿真,得到的生命周期数据如下:
| 协议 | 首个节点死亡轮数 | 10%节点死亡轮数 | 半数节点死亡轮数 |
|---|---|---|---|
| LEACH(单跳) | 980 | 1350 | 2240 |
| EEUC(非均匀多跳) | 1585 | 2030 | 2950 |
| 本文协议 | 1710 | 2260 | 3012 |
从两个维度解读这组数据:
在网络存活初期,本文协议的首个节点死亡轮数比LEACH提高了约74%,比EEUC提高了约8%,说明它在缓解热区效应方面确实有效。在网络运行中后期,半数节点死亡轮数与EEUC相比提升幅度不大,说明在远端能量耗尽阶段,两者的能量消耗方式逐渐趋同。
我还额外统计了一个指标——网络死亡节点空间分布情况。LEACH的死亡节点在Sink附近高度集中,呈现出明显的“环状死亡带”;而本文协议和EEUC的死亡节点相对均匀地分布在整个网络区域。这个差异在热力图上肉眼可见,也验证了非均匀分簇在能量均衡方面的核心优势。
4.4 为什么选择Matlab而不是其他工具?
做WSN协议仿真,可选工具其实不少,比如NS-2、NS-3、OMNeT++,还有OMNeT++里比较流行的INES框架。Matlab在科研算法验证阶段有着独特的优势:
一是上手门槛低。Matlab的语法相对接近数学表达,不需要像NS-3那样写大量C++代码和TCL脚本。对于需要频繁调整算法逻辑的研究者来说,开发效率要高很多。
二是可视化方便。plot、scatter、imagesc等函数可以快速绘制节点分布、能耗热力图、生命周期曲线,做演示汇报时非常直观。
三是调试灵活。Matlab的断点调试和命令行交互模式让定位算法问题变得很快。这一点在调试分簇算法的边界条件时尤为重要。
当然,Matlab的缺点也很明显:大规模网络(上千个节点)的仿真速度偏慢,且无法模拟复杂的MAC层协议和物理层干扰。但如果只关注路由层算法设计,Matlab完全够用,是性价比最高的选择。
5. 仿真中遇到的典型问题与排查方法
5.1 节点能量计算出现负值
我在仿真中第一次跑完整流程时,发现部分节点能量变为负数。排查后确认原因是数据传输阶段能耗累加时,没有在发送前检查节点剩余能量是否充足。比如一个节点剩余能量只有0.002J,但这次发送需要消耗0.005J,节点直接进入负能量状态。
这个问题很隐蔽,因为它不会导致仿真崩溃,但会污染统计数据。最终的处理方式是:在能量扣减之前检查该次通信所需能量是否满足,如果不满足,节点缓存数据并进入休眠状态,等待下一轮再尝试发送。这样可以更真实地模拟节点在能量不足时的表现。
5.2 簇首选举陷入死循环
第二个问题是簇首选举阶段偶尔出现死循环。原因是节点A和节点B同时竞选簇首,两者的竞争半径互不覆盖,但在延迟时间先后顺序上存在竞争关系,导致节点在等待状态中无法做出最终决定。
我的解决办法是给选举阶段设置一个最大重试轮数。如果节点在设定时间内无法确定自己的角色,自动转为成员节点,保证整个流程可以继续推进。这也是实际网络协议设计中常见的兜底策略。
5.3 多跳路由出现回环
多跳路由构建时,如果只考虑“比自己更靠近Sink的节点作为中继”,理论上不会出现回环。但在仿真代码中,由于浮点数精度问题,个别节点可能会把“距离基本相等”的节点误判为更近节点,从而形成A→B→A的回环。
为了避免这类问题,我在路由选择中加了一个最小距离差阈值:候选中继节点的Sink距离必须比当前节点小至少1米才会被考虑。这个阈值虽小,却有效杜绝了回环现象。
5.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 节点能量出现负值 | 发送前未检查剩余能量是否充足 | 发送前增加能量判断,不足则缓存数据 |
| 簇首选举结束慢或死循环 | 竞选延迟机制缺乏最大时限 | 增加最大等待轮数,超时自动降级为成员节点 |
| 路由出现回环,数据包永远不能到达Sink | 距离比较没有考虑浮点数精度 | 设置最小距离差阈值,禁止差距过小的节点作为中继 |
| 网络生命周期极短 | 参数c设置过大,远端簇首负载过重 | 重新调低c值,观察首节点死亡时间变化 |
| 仿真运行耗时过长 | 每轮都执行全网络能量平均值计算 | 每10轮计算一次平均值,减少计算量 |
5.5 对方案后续扩展的一些建议
这个协议后续还可以从几个方向继续优化:一是引入移动Sink或者多Sink机制,进一步均衡网络能耗;二是在簇首选举阶段引入更强的预测模型,比如基于历史能耗数据预测节点剩余寿命;三是将MAC层和传输层的能量开销纳入模型,让仿真更接近真实环境。这些方向都值得深入尝试。
从我个人经验来看,做WSN路由算法研究,最大的误区是一上来就钻进复杂的数学公式里。先把简单的均匀分簇方案吃透,亲眼看到热区效应给生命周期带来的负面影响,再做非均匀分簇优化,理解会深刻得多。Matlab仿真最大的价值不在于它能把算法跑得多快,而在于它能用直观的图表告诉你:你的算法设计到底是改善了问题,还是制造了新问题。我在调试c参数时最有感触——通过一组对照实验看到非均匀系数与生命周期之间的倒U型关系时,对协议设计的理解就完全不一样了。建议读者拿到这套代码后,先不要急着修改算法逻辑,而是把基础场景跑通、性能指标看明白,然后一点一点调整参数,观察每个参数对结果的影响。这个过程做完,收获会远远超过“能运行出结果”本身。
本文还有配套的精品资源,点击获取