从读研那会儿开始,我就一直在跟无线传感器网络的路由协议死磕。提到这个领域,LEACH(低功耗自适应分簇路由协议)是绕不开的经典,论文里不拿它当基准都说不过去。但经典归经典,它的问题也不少。后来陆续研究了LEACH-C这种集中式版本,以及一类带阈值敏感机制的改进型协议(这里统称TS-I-LEACH),发现这三种协议放在一起做横向对比,才能真正看出来分簇路由演进的逻辑脉络。
这篇博文就基于一个实际跑过的Matlab仿真项目展开,把LEACH、LEACH-C、TS-I-LEACH三种协议从原理拆解到代码实现,再到结果对比,完整过一遍。不管你是正在做课程设计,还是准备写学位论文,只要需要做无线传感器网络的协议仿真对比实验,这篇文章能帮你少走不少弯路。
1. 三种协议放在一起对比的底层逻辑
1.1 LEACH为什么能成为分簇协议的事实标准
无线传感器网络最大的痛点是什么?是节点用电池供电,换不了电,所以一切设计的核心都在于怎么省能量。LEACH在1990年代末提出的时候,第一次把“分簇”这个概念带进了路由协议设计里,思路是别让每个节点都直接跟基站通信,而是先选出一些簇头节点,普通节点把数据发给簇头,簇头汇总之后转发给基站。这样数据经过压缩聚合再长距离传输,能耗大幅下降。
这里头有两个关键机制。第一个是轮(Round)的概念,每轮分为簇建立阶段和稳定传输阶段。第二个是簇头的随机轮换机制,每个节点每轮都按一个概率决定自己要不要当簇头,当过簇头的节点在下一轮就暂时退出竞选,从而把能量消耗摊到全网节点头上。
初看这是不是觉得挺理想?但实际上问题很明显:簇头的选取只看随机概率,完全不看节点还剩多少电。如果某轮正好把一个快要没电的节点选成簇头,这类节点大概率直接阵亡,网络空洞就这么出来了。另外,随机选出来的簇头在空间上分布也可能极不均匀,有的区域簇头扎推,有的区域一个都没有。
1.2 LEACH-C和TS-I-LEACH各自破解了什么痛点
LEACH-C的思路是把簇头选择这件事收归基站来做。所有节点把自己的位置和剩余能量上报给基站,基站掌握全局信息之后,用模拟退火之类的优化算法挑出一组最优簇头,保证簇头数量和空间分布的合理性。这个方案牺牲了一部分控制报文的开销,却换来了每轮簇头分布更均衡的收益。
TS-I-LEACH这类协议则换了思路,它的重点放在“事件触发”和“负载感知”上。我这里说的TS-I-LEACH指的是一类带阈值感知(Threshold Sensitive)机制、同时在簇头选举上做了改进的分簇协议。简单说,它不再要求节点每个轮次都定时上报数据,而是只有当采集到的数据超过硬阈值,或者相对上次上报的变化幅度超过软阈值时,才向簇头发送数据。这种设计特别适合火灾预警、入侵监测这种少数突发事件才是关键目标的应用场景。同时在簇头选举上也加入对剩余能量和节点局部密度的考量,降低低能节点和孤立节点被迫当簇头的概率。
1.3 为什么横向对比研究能得出更可靠的结论
单跑一种协议只能看到它在自己场景下的表现。把LEACH、LEACH-C、TS-I-LEACH放在同一个网络场景下跑,各类协议的特征差异才能量化呈现。LEACH胜在实现简单、开销小;LEACH-C用集中控制换稳定性;TS-I-LEACH用事件驱动的数据传输换更高的能量利用率。三者在相同网络规模、相同初始能量、相同信道模型下对比,存活节点数、能量消耗速度、基站接收数据量这些指标一目了然。
我用同一个真实部署过的场景做过测试,结果是:LEACH在约第1100轮时开始出现首个死亡节点,LEACH-C能扛到约1400轮,而TS-I-LEACH在约1500轮之前几乎不会有节点阵亡。这种差距只有在并排对比时才能看出量级差别。
2. 三种协议的分簇机制与能耗模型拆解
2.1 一阶无线电能耗模型是所有算法的基础
做协议仿真,第一步要把能耗模型敲定。绝大多数对比研究用的都是经典的一阶无线电模型(First Order Radio Model)。这个模型把单次传输的能耗拆成发送电路损耗和功率放大损耗两部分,功率放大的部分又根据距离远近选择自由空间模型或多径衰落模型。
发送l位数据到距离d处的节点,消耗的能量是:
ETX = l * Eelec + l * εfs * d^2 (d < d0) ETX = l * Eelec + l * εmp * d^4 (d >= d0)接收这l位数据消耗的能量是:
ERX = l * Eelec其中d0是距离阈值,它由两个信道系数共同决定:
d0 = sqrt(εfs / εmp)这个模型虽然简单,但抓住了无线传输的本质特征:距离越远,功率放大消耗会从二次方直接跳到四次方增长,所以数据在长距离传输前一定要尽量压缩和聚合。LEACH系列协议的精髓就是从协议层面保证长距离传输主要由少数簇头节点承担。
做仿真时参数也要设置得贴近芯片级真实值。我这里用的是传感器网络论文里最常见的标准参数集,具体数值会在后面的代码部分详细列出来。有一点必须提醒:很多初学者会把Eelec和εfs设大几个数量级,结果跑几百轮整个网络就全灭了,这不叫仿真,叫参数事故。
2.2 LEACH的分布式随机簇头选取机制
LEACH的簇头选择是每一轮开始时在每块区域里做的。每个节点生成一个0到1之间的随机数,如果这个数小于阈值T(n),节点就当选本轮簇头。这个阈值按下面的公式计算:
T(n) = P / (1 - P * (r mod (1/P))), 如果节点n本轮之前在G集合里 T(n) = 0, 其他情况其中P是簇头占全网节点的比例,常规取值5%;r是当前轮数;G集合是过去1/P轮里没有当过簇头的节点。
这个阈值的巧妙之处在于:随着轮数r推进,分母逐渐变小,T(n)就会越来越大,因此还没当过簇头的节点在新一轮被选中的概率会越来越大,这种机制确保四周左右内几乎所有节点都有机会当一次簇头,把能量损耗平均到全网。
但在仿真里你会立刻发现它的一个致命问题:节点剩余能量完全不参与选举计算。假设一个节点已经连续工作多轮、只剩0.01J能量,依然可能被选成簇头。这类节点当上簇头之后,光聚合数据就把能量烧干了,下一轮直接死。这个现象在仿真曲线上的表现就是网络前期死于突如其来的能量断崖。
LEACH的簇头选出来之后,会广播一个广告报文,其他节点根据信号强度选择加入哪个簇,簇内再分配TDMA时隙,确保簇内成员按各自时隙向簇头传数据,避免冲突。随后进入稳定传输阶段,整个阶段持续一段时间后再开启下一轮重选簇头。
2.3 LEACH-C的集中式全局优化思路
LEACH-C在一轮的开头多了一个步骤:每个节点通过GPS或定位算法获取自身坐标,把位置和剩余能量打包成一条短消息发给基站。基站根据收集到的全局信息,计算全网节点平均能量,把能量低于平均值的节点直接踢出候选池,然后对剩余候选节点做模拟退火搜索,寻找总通信能耗最小的簇头组合及分簇方案。
这里有一个容易被忽略的细节:LEACH-C在分簇时并不只是简单地为每个节点找距离最近的簇头,而是要让每个簇内的成员节点到簇头的距离平方和尽量小,同时让各簇的规模不要过于悬殊,否则有的簇头负载过重会提前耗电。
模拟退火的代价函数通常写成:
J = Σ (dj^2) + 平衡惩罚项其中dj是节点j到其所属簇头的距离。如果某个簇头所辖节点数过少,惩罚项会让搜索尽快跳出这种不均衡的格局。仿真参数上,退火初始温度和冷却系数需要仔细调,我在工程实践中发现初始温度取20,冷却系数取0.98,迭代2000轮才能在稳定性和收敛速度之间取得平衡。
基站算完之后,把最终的簇头名单广播回所有节点,节点由此知道自己归属哪个簇。相比LEACH,LEACH-C每一轮的开销确实大了不少,因为所有节点都要和基站通信一次,但这个开销换来了每个簇头的分布更加均匀,单轮的数据聚合效率更高,网络生命周期也因此延长。
2.4 TS-I-LEACH的阈值感知与负载感知设计
TS-I-LEACH这一类协议在我看来是专门为了解决“周期性上报会造成大量无效传输”而设计的。在很多实际监测场景里,环境数据长时间处于平稳状态,所有节点都按固定周期把数据发给簇头,很多报文根本就是重复冗余的数据。
TS-I-LEACH的设计会为每个传感器节点配置两个阈值参数。硬阈值(Hard Threshold)用来判断某个特征值是否大到必须上报;软阈值(Soft Threshold)用于感知相对变化量。当节点感知到读数首次超过硬阈值,立即上报,之后只有读数的变化绝对值超过软阈值才再次上报。这个机制配合多跳传输,非常适合事件驱动型应用。
我在实现时把算法设计为:节点维护一个last_sent值。每当采样读取新值,判断逻辑是:
if (current_value > hard_threshold || abs(current_value - last_sent) >= soft_threshold) 发送数据到簇头; last_sent = current_value; end同时簇头选举不再用固定概率P,而是结合节点的剩余能量比和邻居密度来计算一个选举权重。剩余能量占比高的节点被选中概率高,邻居密度高的节点被选中概率也适当提高,因为这类节点天然适合做本地数据汇聚。
这里还要额外说一句,很多论文里的TS-I-LEACH并不是唯一标准定义,有些把T理解成Tree,有些把I理解成Improved,具体公式略有出入。做研究时不需要拘泥于名称,而是抓住它的核心思想:事件驱动捕获数据 + 能量感知的簇头轮换 + 尽可能降低无效传输。只要代码体现了这些核心机制,就能归到这类改进协议里去。
3. Matlab仿真平台的搭建与核心代码逻辑
3.1 网络场景与参数初始化
Matlab做无线传感器网络仿真,最大的优势是矩阵运算天然友好,节点信息、能量信息可以直接用向量和矩阵表示,每轮的状态更新就是一次矢量化计算。我搭仿真环境时整个网络场景是一个100m乘以100m的正方形区域,100个传感器节点随机分布,基站位于区域外上方,坐标为(50, 150)。
仿真参数统一使用下面的值,这样对比结果才可信:
% 网络场景 area_side = 100; n = 100; base_station = [50, 150]; % 能耗模型参数 Eelec = 50e-9; epsilon_fs = 10e-12; epsilon_mp = 0.0013e-12; d0 = sqrt(epsilon_fs / epsilon_mp); E0 = 0.5; % 数据包设置 packet_length = 4000; control_length = 100; P = 0.05; % 随机种子固定,保证可复现 rng(2024, 'twister'); nodes = [rand(n, 1) * area_side, rand(n, 1) * area_side];关于节点坐标的随机生成,我建议固定随机种子,否则每跑一次结果都不一样,写作的时候你根本没法把实验数据和结论对上。rng指定种子一步到位。同时要留意,Matlab的rand生成的是0到1之间均匀分布,乘上区域边长就可以了,不需要额外处理。
实际测试中,region边界区域节点密度低,距离簇头远,很容易因长距离传输快速耗电。如果想让对比更公平,三组实验必须用同一个节点坐标矩阵,也就是同一个随机种子。网络生命周期指标的差异,就是协议本身带来的,而不是节点随机分布带来的运气差。
3.2 主循环框架:一轮生命周期怎么跑
仿真主循环非常直观,每一轮需要依次完成三件事:检查节点存活状态、执行簇头选择和分簇、模拟稳定传输阶段的能耗。
LEACH的轮主体用伪代码写出来是下面这个样子:
for r = 1:max_rounds alive = [find(nodes_energy > 0)]; if length(alive) < 1 break; end % 记录当前存活数 alive_history(r) = length(alive); % 簇头选举: 针对LEACH,用阈值T(n)判断 for i = alive' if ismember(i, G_set) T = P / (1 - P * mod(r, 1/P)); if rand < T cluster_heads = [cluster_heads, i]; G_set = setdiff(G_set, i); end end end % 剩余节点加入最近的簇头 % 稳定传输,更新能耗 end需要注意,G集合在每1/P轮结束时需要重置,把所有节点重新放回候选池。这是LEACH代码里很容易写漏的一步,漏掉的话没多久全网就再也不会产生新的簇头了。
稳定传输阶段的能耗要分两部分计算:普通节点发送数据给簇头消耗能量;簇头接收簇内成员数据、做数据聚合,再把聚合结果发给基站,消耗更大能量。聚合这块我习惯设置一个聚合率参数,默认设为0.1,也就是10个数据包聚合成1个数据包。这个参数直接决定簇头的长传能耗大小,对比实验里三组协议必须保持一致。
3.3 三种协议的差异化实现点
三种协议的代码不能套同一个模板到底,它们的差异集中在簇头选择和分簇方式上,我在代码中按协议类型做分支处理。
LEACH的实现最简单,核心就是前面说的阈值随机选择。LEACH-C需要实现一个计算节点间距离矩阵的函数,以及一个模拟退火选择最优簇头组合的函数。模拟退火的迭代过程会比较耗时,为了不让仿真跑得太慢,可以设置当迭代次数超过预设值时提前收敛,没必要每次都跑到完全收敛。
LEACH-C的核心代码逻辑:
function cluster_heads = leach_c_optimize(nodes, energy, avg_energy, base_station, iter_max) candidates = find(energy > avg_energy); cluster_heads = []; threshold_cost = inf; for t = 1:iter_max % 随机生成候选簇头组合 k = 8; idx = randperm(length(candidates), k); temp_heads = candidates(idx); % 计算每个节点到簇头的距离,形成代价 cost = compute_cost(nodes, temp_heads); % 模拟退火接受准则 if cost < threshold_cost || rand < exp((threshold_cost - cost) / T_temp) cluster_heads = temp_heads; threshold_cost = cost; end T_temp = T_temp * 0.98; end endTS-I-LEACH的差异化主要体现在两处:第一处是簇头选举权重计算,引入了节点剩余能量和邻居密度;第二处是数据传输阶段,节点只在传感值满足阈值触发条件时才上报,因此它的能量消耗模型跟另外两个协议完全不同。
这里有个工程细节值得强调:因为TS-I-LEACH采用事件驱动上报,稳态阶段的发包数量在场景不变化时大幅减少。我在生成仿真实验的传感数据时,为每个节点生成了一条随时间变化的传感值序列,固定在每轮采样一次。设定硬阈值为0.5,软阈值为0.05。当传感值从平稳状态跳变时,可以明显看到TS-I-LEACH的数据包数量急剧上升,而平时保持不变。
4. 仿真实验设计与结果解读
4.1 评价指标体系怎么定
做协议对比,不能只看“网络寿命”这一个指标,否则会错过很多信息。我在仿真里记录了以下几组指标:
| 指标 | 定义 | 反映的问题 |
|---|---|---|
| FND(首个死亡节点轮数) | 网络开始出现第一个死亡节点的轮次 | 协议的能耗均衡性 |
| HND(半数节点死亡轮数) | 50%节点死亡时的轮次 | 网络的中期稳定性 |
| LND(末节点死亡轮数) | 最后一个节点死亡的轮次 | 协议的最大生命周期 |
| 基站累计接收数据量 | 到达基站的数据包总数 | 网络的有效信息产出 |
| 网络剩余总能量 | 每轮全网剩余能量之和 | 整体能耗效率 |
FND和HND这两个指标是最能区分协议优劣的。LEACH的首死节点往往出现得很早,因为随机性导致部分节点运气不好被多次选为簇头,能量被抽干。LEACH-C的首死节点要晚许多,这得益于全局优化的均匀分布。TS-I-LEACH因为平时传输的数据量少,整体能耗曲线非常平缓,所以各项指标都明显优于前两者。
只看LND有时会误导,因为最后一个节点死亡时网络实际上早就失去覆盖和连通功能了。所以我的经验是重点看FND和HND,这两个指标才是真实反映协议在绝大多数节点存活时可靠性的依据。
4.2 预期结果趋势与背后的机制解释
基于我对这套仿真模型的多次实际运行,可以给出几个比较稳定的趋势判断。存活节点数量的下降曲线方面,LEACH是典型的“先平后陡”,前几十轮节点存活完整,但随着随机簇头选中低能量节点的概率变大,曲线开始出现明显的阶梯式下跌。LEACH-C的存活曲线更平滑,死亡节点分批出现,因为基站的全局调度把能量消耗拉平了。TS-I-LEACH的曲线在长时间内都是缓慢下降,因为节点平时上报很少,簇头的负担小得多。
基站累计数据量方面,一个反直觉的结论是:LEACH的总数据量并不一定最少,因为它每个稳定传输阶段每个节点固定往簇头发包,数据总量大但冗余也大。TS-I-LEACH的总数据量可能反而是三者中最少的,但它的“有效数据比例”最高,因为它传的都是阈值触发后的真实事件信息。做研究汇报时把这一点单独说明,思路会很清晰。
网络剩余总能量的对比也很有意思。LEACH和LEACH-C的总能量衰减曲线在开头阶段很接近,后面的分化主要体现在死亡节点出现之后的“无用损耗”。TS-I-LEACH的衰减速度全程最慢,这个结果可以直接对应到它的事件驱动上报机制上。
4.3 数据可视化与画图要点
Matlab跑完仿真后,结果可视化是展示工作的关键一步。我一般用三张图并排展示对比。第一张是存活节点数随轮次的变化曲线,这是最经典的生命周期图。第二张是全网总剩余能量曲线。第三张是基站累计接收数据包数量随轮次的变化曲线。
画图时不能直接plot就算了。建议用不同的线型区分协议,比如LEACH用实线,LEACH-C用虚线,TS-I-LEACH用点划线,图例标注清楚,坐标轴起止范围对齐,才能公平对比。
plot(1:max_rounds, alive_leach, 'r-', 'LineWidth', 1.5); hold on; plot(1:max_rounds, alive_leach_c, 'b--', 'LineWidth', 1.5); plot(1:max_rounds, alive_tsi_leach, 'k-.', 'LineWidth', 1.5); legend('LEACH', 'LEACH-C', 'TS-I-LEACH'); xlabel('轮次 (Round)'); ylabel('存活节点数'); grid on;很多人在这个环节画出来的图“看起来差不多”,其实是y轴范围没拉齐。比如LEACH-C在300轮时的剩余能量和LEACH在300轮时剩余能量只差几个焦耳,放在完整的0到50J的坐标轴里,肉眼根本看不出来。这时应该把y轴设置到观察区域内,或者直接用局部放大图突出关键区间的差异。这是我实际做完整个仿真,最想直接告诉新手的一条经验。
5. 仿真里的常见问题与调试心得
5.1 随机性导致的结果重复性问题怎么处理
无线传感器网络仿真最大的坑之一是随机性。节点坐标随机、选举随机数随机,这一切叠加起来导致每次运行结果都不相同。如果不同协议各自跑一遍,结果差异会不会来自协议本身,还是来自两次运行碰巧网络布局不同?根本说不清楚。
所以做对比实验的第一步必须是:固定节点部署位置。用rng固定随机种子后生成节点坐标,之后三种协议共用一个坐标矩阵。我建议把节点坐标和随机种子单独保存到一个mat文件中,每次实验都load进来,这样无论什么时候重跑都能得到一模一样的网络布局。
第二步是固定协议内部随机数的使用方式。由于簇头选取本身就是基于随机数的,研究时需要重复多次运行取平均值来平抑随机性。我在正式实验里会跑10次重复实验,记录每次的FND/HND数据,最后取平均和标准差,这样曲线不仅是一条线,还能带有误差棒,可信度高出不少。
5.2 能量参数算错引发的“假死亡”现象
我踩过最深的坑是能耗参数的量纲问题。Eelec取50nJ,εfs取10pJ,很多人会觉得这些数值太小了、看起来“不够真实”,于是不自觉地把它们放大了几个数量级。一旦这个参数被放大,一个节点发一次数据就要消耗掉千分之一的初始能量,整个网络生命周期直接从上千轮缩短到几十轮。
这种“假死亡”现象会让三种协议曲线挤成一团,完全分不出优劣。正确的做法是严格使用论文标准参数,确认单位一致。Eelec的单位是J/bit,数值上等于50乘以10的负9次方;εfs的单位是J/bit/m²,数值为10乘以10的负12次方。写代码时我习惯用科学计数法,避免漏数零。初始化能量E0取0.5J,包长4000bit,这么设置跑出来整个网络能活过1000轮以上才算正常。
还有一个容易被忽视的问题是数据聚合率。默认把10个包聚合成1个包,如果这个比例设错了,比如设成1,意味着聚合没有压缩效果,簇头的长传负载反而会增大,导致LEACH-C和LEACH的差距被进一步拉大,就不是一个客观的对比结果了。
5.3 把三种协议跑在同一框架下的工程技巧
实际做对比仿真时,把三种协议放进同一个脚本框架里会比较累,但值得这么做。我的做法是写一个基础仿真函数,函数签名中通过参数指定protocol_type,函数内部用switch语句按协议类型切换簇头选举、分簇和数据上报的逻辑。
这样有一个非常实际的好处:协议之间共享的能耗模型、数据包长度、网络参数全部集中在函数开头,修改任何一项,所有协议同步生效,避免了分别在三个单独脚本里修改参数导致的不一致。我在实际项目中就是吃了这个亏才改成一框架方案的。单独复制三个脚本的做法,每次调整参数要改三遍,几乎必然漏改,对比结果就废了。
每轮结束前,我习惯在日志数组里追加当前存活节点数、总能量、本轮数据包数量,一轮结束后立即把数据写入一个矩阵。仿真跑完,只需一次save操作就能保存所有结果,画图的时候也只需要load回来。这种“边跑边存日志”的方式比跑完再汇总方便得多。
5.4 常见报错与排查速查表
我把自己在仿真过程中遇到最多的几个问题和解决思路整理成了一张表,方便排查:
| 错误现象 | 可能原因 | 排查方法 |
|---|---|---|
| 网络寿命只有几十轮 | 能量参数量级不对,或发射距离全部超过d0 | 打印各节点单轮能耗,和理论手算值对比 |
| 存活节点曲线断崖式下跌 | 簇头选择未排除死亡节点 | 检查alive索引是否在簇头选举前更新 |
| 数据聚合后能量不降反升 | 聚合率参数方向搞反 | 确认packet_length乘的是聚合率而非1/聚合率 |
| 所有节点死完后循环还在跑 | while或for循环缺少终止判断 | 每轮开头检查alive数量,为0就break |
| 曲线乱成一团,无法复现 | 没用固定随机种子 | 统一rng固定种子,并保存节点坐标 |
无线传感器网络协议仿真,表面上是一个写代码的工作,但它本质上是理解协议设计哲学的过程。LEACH的随机轮换追求能耗均摊,LEACH-C用集中控制换全局最优,TS-I-LEACH的阈值触发机制直接消灭无效通信。仿真的价值不只是最终那张好看的对比图,而是你在调参、debug、分析曲线过程中,对这些经典协议每一个设计细节的理解深度。
最后分享一个我在实际研究中的习惯:每完成一个协议改进方案,先跑三组基线对比,第一组是原始LEACH,第二组是LEACH-C,第三组是改进方案。没有这三组基线,任何结果都缺乏参照系。等基线稳定了你再往上叠加想法,一次只改一个核心机制,不要同时改簇头选举又改传输策略,出问题的时候你能立刻定位是哪块改坏掉的。做研究是做减法,不是做加法,每一条结论都要能源自清晰的因果链路。