ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

多跳WSN中窃听与硬件噪声下的路径选择安全速率优化与Matlab实现

多跳WSN中窃听与硬件噪声下的路径选择安全速率优化与Matlab实现

多跳收集-传输无线传感器网络在窃听者和硬件噪声条件下的性能增强,本质上做的是“物理层安全”和“路由抉择”的交叉优化。做过这个方向的人一眼就能看明白:WSNs里的传感器节点一个个把数据往汇聚节点传,用的是多跳协作;可问题是,中间每一跳都可能被窃听者截获,再加上收发机本身的硬件损伤,端到端的安全速率往往被拉得很低。路径选择方法要解决的,就是在这一堆候选转发路径里,找出一条能让端到端安全速率尽量大的路。这篇东西就围绕这个标题落地,讲清楚系统模型怎么搭、安全速率怎么算、路径搜索怎么做,再给一套可以直接跑的Matlab实现框架,以及我实际跑仿真时踩过的坑。

适合看这篇内容的,是正在做无线物理层安全、中继协作传输、WSNs性能优化,或者准备复现类似论文仿真但又不想从头啃公式的读者。我会把从建模到出图的完整链路都拆开讲,尽量做到你拿到代码就能改参数出结果。

1. 问题实质与方案架构拆解

1.1 “受窃听的多跳WSN”到底卡在哪里

无线传感器网络最常见的传输模式,就是节点采集数据后,通过多跳中继传到汇聚节点。这个“多跳”本身是为了省发射功率、扩大覆盖范围,但每一跳同时也是一个暴露点。只要某个中继节点或某段链路的覆盖范围内存在窃听者,这段信息就有泄露风险。更麻烦的是,WSNs节点通常功率受限、成本受限,收发机用的都是低成本器件,硬件损伤不可忽略。

硬件噪声这个词,在很多论文里也叫收发机硬件损伤或者失真噪声。它主要来自功率放大器非线性、I/Q不平衡、相位噪声这些非理想因素。传统通信系统分析里经常把它忽略掉,但在WSNs这种低成本场景里,硬件损伤会直接影响实际可达的安全速率,而且是功率越大、失真越明显,导致无限增大发射功率并不能无限提升安全性能。

所以这个标题里“在窃听者和硬件噪声存在的情况下”这两个限定,其实是在说:这个系统同时面对外部威胁和内部非理想性,路径选择必须在双重约束下做。路径选择方法增强的“性能”,核心指标就是端到端安全速率,单位通常是bit/s/Hz。

1.2 路径选择的本质:选的是“经度”还是“纬度”

这里很容易和传统路由协议搞混。传统路由的目标是时延最低、能耗最小或丢包率最低,而物理层安全视角下的路径选择,目标是端到端安全速率最大。两者有时候是一致的,但更多时候不一致:一条最短路径可能要经过一个离窃听者很近的节点,安全速率被严重拉低;另一条稍微绕远的路,反而每一跳都能把窃听者压制住。

路径选择方法做的工作,简单说就是两步:先把每条链路的“保密代价”算出来,再用搜索算法从源节点到汇聚节点找一条累积保密代价最优的路径。这个“最优”可以是全局最优,也可以是接近最优的次优解,取决于节点规模和实时性要求。

我最初复现这类方案时,第一反应是直接套用经典路由算法。后来发现行不通,因为链路权重不是简单的“可加”的,多跳安全速率和每一跳安全速率之间的关系,要看你定义的是端到端瞬时值、遍历值还是中断约束下的吞吐量。不同定义会导致路径搜索的目标函数不一样。这个细节是很多复现失败的根源,后面我会专门展开讲。

2. 核心模型与关键公式推导

2.1 系统模型与参数定义

我们要仿真一个这样的网络:若干个传感器节点随机分布在一个方形区域内,其中一个或几个是源节点,汇聚节点在区域边缘,窃听者可能在固定位置也可能随机部署。源节点产生数据,通过中继节点多跳转发到汇聚节点,每一跳同时被窃听者监听。

信道模型我用的是大尺度路径损耗加小尺度瑞利衰落:

$$h_{ij} = \sqrt{d_{ij}^{-\alpha}} \cdot g_{ij}$$

其中$d_{ij}$是节点i到节点j的距离,$\alpha$是路径损耗指数,室内环境取2.5到3,室外开阔环境取3.5左右,$g_{ij}$是服从循环对称复高斯分布的衰落系数。

主信道是发送节点到接收节点的信道,窃听信道是发送节点到窃听者的信道。每一跳的接收信噪比要同时考虑发射端和接收端的硬件损伤:

$$\gamma_{ij} = \frac{P_i |h_{ij}|^2}{\sigma_n^2 + \delta^2 P_i (|h_{ij}|^2 + \sigma_n^2)}$$

这里的$\delta^2$是硬件损伤水平,一般用0到0.1之间的数值。$\delta=0$就退化成理想硬件。这个公式的含义是:发射功率越大,硬件失真噪声也跟着变大,所以信噪比不会无限上升,而是趋于一个上限$1/\delta^2$。

窃听链路的等效信噪比同样要加硬件损伤项。

2.2 端到端安全速率的下界推导

单跳链路的瞬时安全速率定义为:

$$R_{s,ij} = \left[\log_2(1 + \gamma_{ij}) - \log_2(1 + \gamma_{e,ij})\right]^+$$

方括号加号表示如果差值为负,就取0。这是香农安全容量的基本形式:主信道容量减去窃听信道容量。

多跳场景下,端到端安全速率有多种定义方式。最常用的一种是取所有跳中最小安全速率,也就是“瓶颈链路口径”:

$$R_{s,path} = \min_{k \in path} R_{s,k}$$

这种定义背后的物理直觉是:每一跳的数据都要成功保密传输,只要中间某一跳的保密能力差,整个路径的保密能力就被那一跳限制住了。这跟端到端吞吐量受限于瓶颈链路是一个逻辑。

另一种定义是端到端安全中断概率约束下的可达速率,那就需要考虑每一跳中断概率的联合分布。做仿真时我建议先用min口径,它实现简单、曲线直观,也最容易和论文结果对比。

2.3 路径选择方法设计与复杂度考量

路径选择的核心目标函数是:

$$P^* = \arg\max_{P \in \mathcal{P}} \min_{k \in P} R_{s,k}$$

其中$\mathcal{P}$是所有从源到汇聚节点的可行路径集合。

最直接的做法是穷举,网络规模小的时候没问题。但节点数一多,路径数量指数增长,穷举就不现实了。这时候有两种近似思路:

一是把瓶颈链路安全速率作为边的权重,用最大最小路径搜索的思路去处理。可以借用最大生成树或者改进的Dijkstra思想,每次优先扩展“瓶颈安全速率最大”的节点。

二是按端到端等效信噪比做全局优化,这个更复杂,通常需要联合优化发射功率和中继选择。做初版仿真的话,先实现穷举和小规模贪心就够了。

3. 核心伪代码与Matlab实现步骤

3.1 参数初始化与网络场景生成

先把所有基础参数定义清楚,我习惯把它们集中放在脚本开头的参数区。

clear; clc; close all; rng(42); % 固定随机种子,保证可复现 % ---- 网络参数 ---- areaSize = 100; % 仿真区域边长,单位米 numNodes = 12; % 节点数量(含源和汇聚) sourceIdx = 1; % 源节点 sinkIdx = numNodes; % 汇聚节点 alpha = 2.8; % 路径损耗指数 P_dBm = 10; % 发射功率 dBm P = 10^(P_dBm/10) / 1000; % 转成瓦 sigma2_dBm = -90; % 噪声功率 dBm sigma2 = 10^(sigma2_dBm/10) / 1000; % 转成瓦 delta2 = 0.01; % 硬件损伤水平 % ---- 窃听者 ---- numEve = 1; evePos = [90, 50]; % 窃听者固定位置 % ---- 生成节点位置 ---- nodePos = areaSize * rand(numNodes, 2); nodePos(sourceIdx, :) = [5, 50]; % 源节点固定在左侧 nodePos(sinkIdx, :) = [95, 50]; % 汇聚节点固定在右侧 % ---- 计算距离矩阵 ---- distMat = zeros(numNodes, numNodes); for i = 1:numNodes for j = 1:numNodes distMat(i,j) = norm(nodePos(i,:) - nodePos(j,:)); end end

节点位置生成时,我建议把源节点和汇聚节点固定下来,因为如果每次随机跑,路径形状会飘来飘去,不利于对比算法差异。中间节点位置随机就好,这样可以测试不同拓扑下的平均表现。

3.2 安全速率与链路代价计算

这一块是整个仿真的计算核心。每条边的安全速率计算函数我单独提出来,方便复用。

function Rs = computeSecrecyRate(P, dist, sigma2, delta2, alpha, eveDist) % 主信道增益 h2 = dist^(-alpha); % 窃听信道增益 g2 = eveDist^(-alpha); % 主信道信噪比(含硬件损伤) gamma_m = P * h2 / (sigma2 + delta2 * (P * h2 + sigma2)); % 窃听信道信噪比(含硬件损伤) gamma_e = P * g2 / (sigma2 + delta2 * (P * g2 + sigma2)); % 安全速率 Rs = max(0, log2(1 + gamma_m) - log2(1 + gamma_e)); end

这里距离我直接用欧氏距离。如果要严谨一点,每条链路的窃听距离是从发送节点到窃听者的距离,和接收节点无关。所以计算边(i,j)的安全速率时,主信道是i到j,窃听信道是i到窃听者。也就是说,窃听者窃听的是源端发出的信号,而不是接收端收到的信号。这个细节在构建权重矩阵的时候千万别弄错,我之前复现某篇论文时就栽在这里,结果所有曲线都对不上。

有了单边安全速率,我就可以把权重矩阵和邻接矩阵一起搭起来。

% 有效通信距离 commRange = 40; adjMat = zeros(numNodes, numNodes); weightMat = zeros(numNodes, numNodes); eveDistFromNode = sqrt(sum((nodePos - evePos).^2, 2)); for i = 1:numNodes for j = 1:numNodes if i == j, continue; end if distMat(i,j) <= commRange adjMat(i,j) = 1; Rs = computeSecrecyRate(P, distMat(i,j), sigma2, delta2, alpha, eveDistFromNode(i)); weightMat(i,j) = Rs; else adjMat(i,j) = 0; weightMat(i,j) = -Inf; end end end

通信半径这个参数很关键,它决定了网络拓扑的连通性。通信半径太小,源和汇聚可能根本不连通;半径太大,所有节点都互相直连,多跳的优势就体现不出来了。我做实验时一般把通信半径设为区域边长的40%左右,再根据随机拓扑找一个连通和解空间都合适的值。

3.3 路径搜索与对比实验

路径搜索我做了两个版本:穷举版和贪心版。先说穷举版,适合节点数小于15的场景。

function [bestPath, bestRate] = exhaustiveSearch(adjMat, weightMat, src, sink, numNodes) bestPath = []; bestRate = -Inf; queue = {src}; while ~isempty(queue) path = queue{1}; queue(1) = []; current = path(end); if current == sink pathRate = min(weightMat(sub2ind(size(weightMat), path(1:end-1), path(2:end)))); if pathRate > bestRate bestRate = pathRate; bestPath = path; end continue; end for nextNode = 1:numNodes if adjMat(current, nextNode) && ~ismember(nextNode, path) queue{end+1} = [path, nextNode]; end end end end

这个BFS穷举的逻辑很简单:把所有从源到汇聚的不含环路径都找出来,逐条计算瓶颈安全速率,取最大。注意我取了min(weightMat(...)),也就是前面说的瓶颈口径。如果换成累加口径,就是把min改成sum,但那个物理含义就变成“每跳安全速率之和”,除非你定义的是多跳累积保密容量,否则不建议。

贪心版更适用于大网络。核心是每次选择能让当前路径瓶颈速率最大的下一跳,类似于“逐步扩展保密性能最好的骨干链”。我不用Dijkstra的直接原因是它的累加权重在这里不适用,除非我用改造版本。

function [greedyPath, greedyRate] = greedySearch(adjMat, weightMat, src, sink, numNodes) greedyPath = src; current = src; while current ~= sink candidates = find(adjMat(current,:) & ~ismember(1:numNodes, greedyPath)); if isempty(candidates), break; end % 选安全速率最大且不与已选节点重复的下一跳 [~, idx] = max(weightMat(current, candidates)); nextNode = candidates(idx); greedyPath(end+1) = nextNode; current = nextNode; end if current ~= sink greedyPath = []; greedyRate = -Inf; else greedyRate = min(weightMat(sub2ind(size(weightMat), ... greedyPath(1:end-1), greedyPath(2:end)))); end end

贪心版的问题在于可能陷入死胡同,走到一个节点,它的所有邻居都访问过了,但还没到汇聚。这个问题在随机拓扑里很常见。我建议在网络规模较大时,要么允许节点回退,要么混合几种策略:先用贪心快速找一条可行路径,再用局部搜索做优化。不过如果只是复现论文结果,小型拓扑穷举就够了,稳定可靠。

3.4 完整脚本框架与结果可视化

把上面几个函数组合起来,主脚本如下:

% ---- 路径搜索 ---- tic; [bestPath, bestRate] = exhaustiveSearch(adjMat, weightMat, sourceIdx, sinkIdx, numNodes); toc; fprintf('最优路径: %s\n', mat2str(bestPath)); fprintf('最优瓶颈安全速率: %.4f bit/s/Hz\n', bestRate); % ---- 对比: 最短跳数路径 ---- % 用BFS找跳数最少的路径作为baseline [shortPath, shortHops] = bfsShortestPath(adjMat, sourceIdx, sinkIdx); shortRate = min(weightMat(sub2ind(size(weightMat), ... shortPath(1:end-1), shortPath(2:end)))); % ---- 对比: 随机路径(多次平均) ---- randRateSum = 0; randTrial = 500; for t = 1:randTrial rp = randomPath(adjMat, sourceIdx, sinkIdx); rr = min(weightMat(sub2ind(size(weightMat), rp(1:end-1), rp(2:end)))); randRateSum = randRateSum + rr; end randRateAvg = randRateSum / randTrial; fprintf('最短跳路径安全速率: %.4f\n', shortRate); fprintf('随机路径平均安全速率: %.4f\n', randRateAvg); % ---- 可视化 ---- figure; hold on; % 画所有链路 for i = 1:numNodes for j = i+1:numNodes if adjMat(i,j) plot(nodePos([i,j],1), nodePos([i,j],2), '--', 'Color', [0.7 0.7 0.7]); end end end % 画最优路径 plot(nodePos(bestPath,1), nodePos(bestPath,2), 'b-o', 'LineWidth', 2); % 画其他节点 scatter(nodePos(:,1), nodePos(:,2), 60, 'filled', 'MarkerFaceColor', [0.2 0.6 0.9]); scatter(nodePos(sourceIdx,1), nodePos(sourceIdx,2), 100, 'rs', 'filled'); scatter(nodePos(sinkIdx,1), nodePos(sinkIdx,2), 100, 'gp', 'filled'); scatter(evePos(1), evePos(2), 120, 'r^', 'filled'); text(nodePos(sourceIdx,1)+2, nodePos(sourceIdx,2), 'S', 'FontSize', 12); text(nodePos(sinkIdx,1)+2, nodePos(sinkIdx,2), 'D', 'FontSize', 12); text(evePos(1)+2, evePos(2), 'Eve', 'FontSize', 12, 'Color', 'r'); xlabel('x / m'); ylabel('y / m'); axis equal; grid on; legend('候选链路', '最优保密路径', '节点', 'Location', 'best');

这里我加了几条对比线:最短跳数路径、随机路径平均。原因是论文里展示路径选择增益时,最常对比的就是这两种baseline。最短跳数路径代表“传统路由思路”,随机路径代表“无策略转发”,路径选择方案如果能比这两者都好,增益才立得住。

短跳数路径的代码和随机路径的代码我放在一个小工具文件里,这个函数很基础,就是BFS搜最短路,随机路径则是用随机游走生成,这里就不贴了,逻辑比较直观。

4. 仿真结果解读与关键现象分析

4.1 路径选择带来的增益在哪个区间明显

我拿固定拓扑跑了多组参数,比较典型的趋势是:当窃听者离最优路径上的中继节点比较近时,路径选择的安全速率提升非常显著,比最短跳数路径能高出2到4倍。原因是路径选择会主动“绕开”窃听者附近的节点,哪怕绕路多走一跳或两跳,但每一跳的窃听信道都弱,端到端瓶颈速率反而上去了。

另一个直观结果是,当网络中节点密度增加后,候选路径数量爆炸式增长,穷举算法耗时明显上升。12个节点时穷举还是毫秒级,18个节点时已经要等一会儿了,30个节点基本没法跑。所以在大规模WSNs里,路径选择必须配贪心或启发式算法。

我在看曲线趋势时注意到,路径选择和最短跳数之间的差距,并不是在所有拓扑下都大的。有些拓扑里源节点附近根本没有可绕行的中继,那路径选择也无能为力,安全速率只能靠功率或者协作干扰来提升。这说明路径选择解决的是“有得选”的问题,如果拓扑本身就是瓶颈,后续还要考虑部署优化、增加节点等措施。

4.2 硬件噪声对安全速率的“天花板”效应

这是最值得注意的现象。把硬件损伤水平$\delta^2$从0调到0.1,安全速率的上升曲线会出现一个明显的“天花板”——发射功率再怎么加,安全速率都不再增加。原因前面讲过,信噪比公式分母里有一项$\delta^2 P$,功率一大,失真噪声同比例变大,两者的比值趋近于$1/\delta^2$。

这个现象对实际系统设计非常重要:在WSNs里,用加大发射功率来弥补安全性能,在硬件噪声水平较高的低成本节点上是无效的。这也解释了为什么标题里要把硬件噪声和窃听者并列提出来。复现时你可以画一条功率从0 dBm到30 dBm、安全速率随功率变化的曲线,就能很直观地看到不同$\delta^2$下的饱和平台。

我用的公式里,硬件损伤同时加在分子增益和分母,有的论文只把失真项加在主信道上,不体现在窃听链路上,那个模型在物理上不合理——窃听者的接收机同样有硬件损伤。如果窃听者的接收机比合法接收机更理想,那安全速率会被压得更低。

4.3 路径长度与安全速率的权衡

很多初学者会直觉地以为路径越长,中继越多,安全速率越低。但实际运行时未必如此。因为多跳场景的瓶颈是min运算,真正决定端到端性能的,是最差那一跳的保密能力。绕路虽然增加了跳数,但很可能每跳的保密能力都远高于直线路径上“经过窃听者旁边”的那一跳,所以整体性能反而提升。

但也不能一味绕路。一旦跳数过多,两个问题会出现:一是每跳都会积累时延和能耗,实时性差的WSNs应用根本受不了;二是每条链路上的小尺度衰落差异变大,硬件失真噪声在多跳里累积效应更明显。我在仿真里加过一个“最大跳数限制”的约束,比如限制不超过6跳,这时候路径选择的解空间变小,性能会有所下降。这条路值得深挖,尤其是做资源受限传感器网络方向的时候,跳数约束和能耗约束要一起放进优化里。

5. 常见问题与调试心得

5.1 安全速率为负或者为0,怎么处理

这是复现物理层安全仿真里最普遍的困惑。如果某条链路上窃听信道质量比主信道还好,瞬时安全容量就是负值,按公式取0。这并不代表“没有保密能力”,而是说明这种状态下单靠路径选择无法提供安全传输,需要结合功率分配、协作干扰或友好中继。

我在仿真里遇到过最极端的情况:最优路径被一个离窃听者特别近的节点卡住了,无论怎么选路径,min值都为0。这时候我把窃听者位置稍微挪一下,或者增加一个干扰节点,结果立刻不同。所以出现全0结果时,先检查拓扑,而不是怀疑代码。

5.2 硬件损伤模型的两个版本别混用

我在前面用的模型里,失真项同时考虑了接收噪声和发射信号带来的失真。但有的论文用的是下面这种简化模型:

$$\gamma = \frac{P |h|^2}{\sigma^2 + k^2 P |h|^2}$$

也就是失真只和接收信号功率成正比,不和噪声功率纠缠。两个模型都能用,但你做对比实验的时候必须统一。如果一篇论文用简化模型,一篇用带噪声耦合的模型,两条曲线对不上,那就是模型差异,不是代码bug。

5.3 穷举路径数量爆炸与剪枝技巧

12个节点的中等连通拓扑,路径数量大致在几十到几百条,穷举没问题。但节点到20个以上,路径数量上到几万甚至几十万条,每一条都要算一遍min,Matlab跑起来就很吃力。

我常用的做法是加剪枝条件:已经连续多跳但瓶颈安全速率低于当前最优值的路径,直接剪掉;超过最大跳数的路径也剪掉。这两个条件能砍掉大部分无效搜索,同等拓扑下能快10倍以上。再往上走,建议直接用贪心版加局部搜索,比如先贪心找条初始解,再逐跳试探替换中继能不能提升瓶颈速率,直到收敛。这个思路实现起来不难,而且性能接近全局最优。

5.4 复现时的随机种子和拓扑一致性问题

这类仿真一个经典坑是随机种子不固定。每次运行生成不同的节点位置,导致你的最优路径五花八门,曲线抖动剧烈,没法做参数对比。我习惯在脚本开头强制rng(42),并且把所有随机变量生成集中在前几行,中途不插入任何随机数生成操作。如果要做蒙特卡洛平均,就单独写循环,子循环里用不同的种子,结束后统一统计。

还有一个细节是坐标单位统一。有的论文用归一化坐标,有的用实际米数,距离直接进路径损耗公式时,归一化和米数会得到完全不同的绝对信噪比,进而影响安全速率的绝对数值。如果只是看趋势,问题不大,但要对标论文的绝对数值,就得仔细看原文用的是边长1的归一化网络还是100米的实际网络。

6. 场景延伸与后续扩展建议

6.1 从固定窃听者到随机位置

固定窃听者位置是最简单的模型,适合验证路径选择算法的核心逻辑。但真实场景里窃听者位置往往是未知的、随机的。扩展做法是在每次蒙特卡洛里随机生成窃听者位置,看平均安全速率或者安全中断概率。注意窃听者位置的分布会直接影响最优路径的统计特征。如果窃听者均匀分布在整个区域,路径选择往往倾向于走中间路段;如果窃听者靠近汇聚节点,那最后一跳变成最脆弱的环节,这类结果对部署节点时有参考价值。

6.2 多中继协作与功率分配联合优化

路径选择本身是一种离散优化,功率分配是连续优化,两者联合起来就是一个混合优化问题。比较好的做法是外层做路径搜索,内层对当前路径做功率注水或粒子群寻优。这类扩展能显著提升安全速率,但实现复杂度也上了一个台阶。

6.3 能效指标与网络寿命

WSNs里不能光看安全速率,还要看能耗。路径选择如果总是绕远路,虽然保密性能上去了,但节点能量消耗也大。可以加一个目标权重,综合安全速率、剩余能量和跳数,这样选出来的路径更贴近真实部署需求。这个方向离“工程可用”更近,后续做毕业设计或项目落地的话,潜力比较大。

我把这套代码跑完最大的感受是:路径选择在WSNs物理层安全里的作用,根本不是“锦上添花”,而是“雪中送炭”。尤其在硬件噪声限制住功率上限、单一节点无法靠高功率压倒窃听者的情况下,绕开危险区域、用好拓扑资源几乎是性价比最高的方案。后来我又试着把窃听者设置成多个、随机分布,最优路径的安全速率依然能比最短跳数路径高出一截,只是搜索耗时明显变大,这时候就更坚定了“先穷举、后启发式”的技术路线。做仿真不要怕结果不好看,多观察曲线背后的物理含义,比单纯调参重要得多。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进