<think>我们使用OMNeT++进行仿真,由于OMNeT++使用C++编程,我们将实现一个简单的洪泛路由算法(Flooding)来完成任务。
注意:洪泛算法虽然简单,但效率低,能量消耗大,这里仅作为示例。
实验要求:
1. 圆形区域,半径500m,Sink在圆心(0,0)。
2. 500个节点均匀随机部署。
3. 每个节点传感半径50m(通信半径也设为50m?注意题目说单位圆内节点均能相互通信,即通信半径50m)。
4. 数据包:消息报50byte,数据包500byte?这里我们注意:节点采集的数据包大小为500byte,但可能路由协议会使用控制报文(如洪泛中的广播)为50byte?题目没有明确,我们假设:
- 节点在发送数据包时,使用500byte的数据包(即有效载荷)。
- 路由协议的控制消息(如洪泛中用于广播路由请求)使用50byte的消息报?但洪泛算法通常直接广播数据包,所以可能没有单独的控制消息。因此,我们简化:所有发送的数据包都是500byte(即数据包)。
5. 周期T=100ms,节点在每个周期内随机时刻发送数据。
能量消耗模型:
发送能量:当传输距离d<40m时,Esend = l * Eelec + l * efs * d^2
当d>=40m时,Esend = l * Eelec + l * eamp * d^4
接收能量:Ereceive = l * Eelec
参数:
l = 100 bits? 注意表1中l(传输l比特)为100,但是数据包大小是500byte,即4000bits。这里存在矛盾?我们需要明确:
题目中数据包大小为500byte,即4000bits。但是表1中l(传输l比特)为100,这里可能是指每次传输的比特数?但显然数据包是500byte,所以l应该为4000bits。
重新审视表1:
参数名称 | 值
l(传输l比特) | 100 -> 这个100可能是示例值?实际上我们每次发送一个数据包(500byte)就是4000比特。因此,在计算能量时,l=4000(比特)。
但是,注意题目描述中消息报大小为50byte(400比特),数据包大小为500byte(4000比特)。那么什么时候用消息报?什么时候用数据包?
根据实验内容:统计各节点产生、发送、接收的数据包个数。这里我们理解:
- 节点产生的数据是数据包(500byte),即应用层数据。
- 在传输过程中,我们可能使用路由协议,可能会产生控制消息(50byte)。但是洪泛算法中,节点直接广播数据包,所以没有单独的控制消息。因此,我们假设所有传输的都是数据包(500byte)?
但是,题目要求实现“周期性收集”,节点在周期内发送数据包,因此我们假设节点每次发送一个500byte的数据包。
因此,在能量计算中:
发送一个数据包:l = 500 * 8 = 4000 bits
接收一个数据包:l = 4000 bits
能量参数:
Eelec = 50 nJ/bit = 5e-8 J/bit
efs = 10 pJ/bit/m2 = 1e-11 J/bit/m2
eamp = 0.0013 pJ/bit/m4 = 1.3e-15 J/bit/m4
发送能量公式(注意单位转换):
d < d0 (40m):
Esend = 4000 * (50e-9) + 4000 * (10e-12) * d^2 [单位:焦耳]
= 4000 * 5e-8 + 4000 * 1e-11 * d^2
= 0.0002 + 4e-8 * d^2
d >=40m:
Esend = 4000 * (50e-9) + 4000 * (0.0013e-12) * d^4
= 0.0002 + 4000 * 1.3e-15 * d^4
= 0.0002 + 5.2e-12 * d^4
接收能量:
Ereceive = 4000 * 50e-9 = 0.0002 J
节点初始能量:0.5 J
仿真过程:
1. 初始化网络:在半径为500m的圆内随机均匀部署500个节点,Sink节点在(0,0)。
2. 每个节点在周期T(100ms)内随机选择一个时刻发送一个数据包(目的地址为Sink节点)。注意:由于洪泛算法,节点会广播数据包。
3. 使用洪泛算法:节点收到一个数据包后,如果不是目的节点且该数据包是第一次收到,则广播该数据包(同时记录已转发过,避免重复广播)。Sink节点收到后不广播。
4. 每个节点在发送和接收数据包时消耗能量。当节点能量不足以发送或接收一个数据包时,该节点死亡(不再发送和接收)。
5. 当网络中第一个节点死亡时,仿真停止。记录每个节点的:
- 产生的数据包个数(即应用层发送的数据包数,每个节点每个周期产生一个)
- 发送的数据包个数(包括自己产生的和转发的)
- 接收的数据包个数(不包括自己发送的,只包括接收到的其他节点发来的)
- 剩余能量
6. Sink节点记录:接收到的来自每个节点的数据包个数(注意:同一个数据包可能被多个路径传输多次到达,但我们只统计第一次到达的?题目要求端到端传输延迟,应该是第一个到达的包)和端到端传输延迟(从源节点发送到Sink节点收到的时间差)。
注意:由于洪泛算法会产生大量重复包,我们只记录每个数据包第一次到达Sink的时间(作为端到端延迟)以及第一个到达Sink的包(后续重复包不计入该源节点的数据包计数?)。但是题目要求“接收到的各传感器节点数据包个数”,这里可以理解为每个节点产生的每个数据包,Sink只要收到一个就算?所以我们对于每个数据包(由源节点和序列号标识),Sink只记录第一次收到的。
实现步骤:
(1)节点数据结构:每个节点需要记录:
- 位置坐标
- 剩余能量
- 邻居列表(由于节点固定,可以在初始化时计算好,即距离小于50m的节点)
- 每个数据包(用源节点ID和序列号标识)是否已经转发过(避免重复广播)
- 发送包计数(自己产生的和转发的都算发送)
- 接收包计数(接收到的包)
- 产生的包计数(作为源节点产生的包,每个周期产生一个)
- 死亡标志
(2)Sink节点:记录每个源节点发送的数据包中,哪些已经收到过(用源节点ID和序列号),以及每个包第一次到达的时间。
(3)事件:每个节点在每个周期内随机时刻(0到100ms之间)产生一个数据包,并广播(发送给所有邻居)。同时,当节点收到一个数据包时,如果不是重复且自己不是目的节点(Sink节点)且自己还有能量,则广播(洪泛)。
(4)能量更新:每次发送或接收一个数据包,更新能量。注意:发送数据包时,消耗的能量取决于发送距离(这里洪泛是广播,但是广播实际上是发送给所有邻居,那么发送一次数据包,所有邻居都会收到?但发送能量计算时,我们如何计算距离?实际上,广播一次,节点消耗的能量是按照它发送数据的最大距离?还是按照每个邻居分别计算?这不符合实际。实际中,广播一次,节点只发送一次,消耗的能量是发送一次的能量,这个能量取决于发送功率,而发送功率需要保证最远的邻居(50m)能收到。因此,我们按照最坏情况,即按照通信半径50m(大于40m,所以用d>=40的公式)来计算发送一次广播的能量。注意:广播一次,所有在通信范围内的节点都会收到,所以发送一次,消耗的能量是按照50m计算一次。而接收方每收到一个数据包,就消耗一次接收能量(0.0002J)。
但是,这里有一个问题:节点发送数据包时,消耗的能量与发送距离有关,而广播的发送距离应该是节点的最大通信距离(50m)?还是实际传输距离?实际上,节点发送时,不管邻居多远,只要在50m内,它都以50m的功率发送(保证最远的邻居也能收到)。因此,我们按照50m来计算发送能量。
发送一次广播(数据包500byte):
d = 50m >=40m, 所以:
Esend = 0.0002 + 5.2e-12 * (50)^4
= 0.0002 + 5.2e-12 * 6250000
= 0.0002 + 0.0000325 = 0.0002325 J
接收一次:0.0002 J
注意:当节点能量低于发送或接收所需能量时,则不能发送或接收。
(5)时间:每个周期100ms,节点在周期内随机时间点发送数据。我们假设仿真时间按事件驱动推进。
仿真流程(事件驱动):
事件类型:
1. 节点产生数据包事件(每个周期一次,每个节点在每个周期内的随机时刻)
2. 数据包传输事件(节点发送数据包,然后邻居节点在传输延迟后收到)
传输延迟:我们忽略传输延迟(因为无线传输速度是光速,50m距离延迟非常小)和传播延迟,但可以加上一个固定的处理延迟(如1ms)?题目没有要求,为了简单,我们不考虑传输延迟(即发送后立即到达邻居节点)。
因此,事件调度:
- 节点在时间t产生一个数据包,然后立即广播(消耗能量,并给所有邻居节点发送数据包,接收节点在t时刻同时收到?但实际中不能同时,我们可以按顺序处理,但时间戳都是t。为了避免同一时刻事件顺序问题,我们可以给每个接收事件加一个极小的时间增量(如1ps)来区分顺序,但OMNeT++有内置的机制处理同时事件。这里我们简化,同一时刻的事件按节点ID顺序处理。
具体实现(OMNeT++):
OMNeT++中,我们创建一个网络模块,包含一个Sink节点和500个传感器节点。
节点模块:定义一个复合模块或简单模块?这里我们使用简单模块,通过编写C++代码实现。
步骤:
1. 定义节点模块(SensorNode)和Sink节点模块(SinkNode,可以继承SensorNode,也可以单独实现,因为Sink节点行为不同,比如不产生数据,且收到数据包后不转发)。
2. 初始化:在initialize()阶段,节点获取自己的位置,计算邻居列表(注意:邻居列表是固定的,因为节点不移动)。注意:邻居是指距离小于等于50m的节点(包括自己吗?不包括,因为自己不能发送给自己)。Sink节点在(0,0),其他节点随机部署在圆内(注意均匀分布)。
3. 每个节点(除了Sink)在第一个周期开始时就安排一个产生数据包的事件(在0到100ms之间的随机时刻)。然后每个周期都安排(周期性事件)。
4. 当节点产生数据包时,创建一个数据包(cMessage),设置源节点ID、序列号(每个节点自己维护一个递增序列号),然后广播(发送给所有邻居节点)。同时,记录这个包是自己产生的(产生包计数加1),并记录发送包计数加1,消耗发送能量。
5. 当节点收到数据包时,首先判断自己是否死亡(能量不足),如果死亡则丢弃。否则,消耗接收能量(接收包计数加1)。然后检查数据包的目的地是否是Sink(如果是,则自己不是Sink,所以需要转发?但注意,数据包的目的地应该是Sink节点(即Sink节点的ID),所以普通节点收到后,如果自己不是Sink,则要广播转发(除非已经转发过)。如果是Sink节点,则记录该数据包(如果这个数据包是第一次收到,则记录源节点和序列号,计算延迟)。
6. 普通节点在决定转发时,先检查这个数据包(源ID和序列号)是否已经转发过,如果没有,则记录为已转发,然后广播(发送包计数加1,消耗发送能量,然后发送给所有邻居)。
注意:在广播时,要排除发送者自己,也要排除死亡节点(死亡节点不会接收)。另外,同一个数据包在同一个节点可能被多次接收(来自不同的邻居),但我们只处理第一次(即第一次收到时会转发,后续收到相同的包就丢弃)。
7. 能量管理:每次发送或接收后,更新剩余能量。如果更新后能量不足以进行下一次发送或接收(即剩余能量小于0.0002325J且下次要发送,或者小于0.0002J且下次要接收),则标记死亡。注意:在死亡时,可能还有已经安排的事件(比如数据包接收事件),我们需要在死亡时取消所有未发生的事件?或者当事件发生时判断死亡状态。我们在事件处理时先判断死亡状态。
8. 仿真停止条件:当第一个节点死亡时,停止仿真。我们可以在每个节点死亡时检查是否是第一个死亡,如果是,则结束仿真。
9. 统计:在仿真结束时,输出每个节点的统计信息(产生包数、发送包数、接收包数、剩余能量)和Sink节点记录的每个源节点的包接收情况(包个数和延迟)。
由于代码量较大,我们只给出关键部分的伪代码和流程图,以及部分核心代码。
流程图:
(由于文本限制,流程图用文字描述)
1. 网络初始化:
- 创建500个节点和一个Sink节点
- 节点随机部署在圆内(注意均匀分布)
- 每个节点计算邻居列表(距离<=50m的节点ID)
2. 每个节点(非Sink)安排第一个数据包产生事件(在[0, T)的随机时间)
3. 仿真时钟推进,处理事件:
- 如果是数据包产生事件:
节点创建数据包(设置源ID、序列号、产生时间)
广播数据包(发送给所有邻居)
安排下一个周期的产生事件(当前时间+T+随机间隔?注意周期是100ms,每个周期内随机时刻,所以下一个周期在T之后,再加上[0,T)的随机时间)
- 如果是数据包接收事件:
节点消耗接收能量,更新剩余能量(如果能量不足,则标记死亡,并取消所有未处理事件,记录死亡时间,如果是第一个死亡的节点,则停止仿真)
如果节点死亡,则丢弃包
否则,如果是Sink节点:
检查这个数据包(源ID,序列号)是否已经记录过,如果没有,则记录(接收包计数加1),并计算延迟(当前时间-产生时间)
否则(普通节点):
检查这个数据包是否已经转发过,如果没有,则记录在已转发列表,然后广播(发送给所有邻居)
- 广播数据包时,节点消耗发送能量,更新剩余能量(同样检查死亡),然后给每个邻居发送一个数据包(同一个数据包,但接收方不同,我们使用OMNeT++的sendDirect方法,每个邻居单独发送,但注意,发送一次广播,发送能量只消耗一次(因为一次发送,多个邻居同时收到)?但在模拟中,我们发送多次(每个邻居一次)?这会导致多次消耗发送能量?不对,实际中一次发送,多个邻居同时收到,所以发送能量只消耗一次。因此,我们在广播前计算一次发送能量,然后消耗。然后为每个邻居创建一个数据包副本(但注意,数据包内容相同,但接收者不同),并安排接收事件(立即安排,延迟为0)。
注意:在OMNeT++中,我们可以使用sendDirect()将消息直接发送到其他模块的gate。在广播时,我们遍历邻居列表(排除自己),为每个邻居创建一个数据包的副本,然后发送。但是,这样会创建多个消息对象。发送一个数据包到多个邻居,相当于一次广播,但是消耗的发送能量应该是一次(按照50m计算)。
代码结构(OMNeT++模块):
- 网络模块:WirelessNetwork.ned
- 节点模块:SensorNode.ned(SinkNode也使用同一个模块,但用参数区分)
由于OMNeT++的详细代码较长,我们只写关键部分的代码(C++部分)。
注意:为了简化,我们假设所有节点都是SensorNode类型,Sink节点通过参数设置(如isSink)。在初始化时,Sink节点被指定在(0,0)。
步骤:
1. 定义消息类型:DataPacket.msg(OMNeT++消息定义文件)
2. 实现节点模块:SensorNode.cc
由于篇幅限制,我们只给出核心逻辑的伪代码(用C++风格)。
DataPacket.msg:
cplusplus {{
#include <omnetpp.h>
}}
packet DataPacket {
int srcId; // 源节点ID
int seq; // 序列号
simtime_t creationTime; // 数据包产生时间
}
SensorNode.h: (略,直接在cc中实现)
SensorNode.cc:
#include <omnetpp.h>
#include <vector>
#include <map>
#include <set>
#include <cmath>
#include <random>
#include "DataPacket_m.h"
using namespace omnetpp;
class SensorNode : public cSimpleModule {
private:
// 参数
double x, y; // 位置
double radius; // 通信半径
bool isSink; // 是否为Sink节点
double initialEnergy; // 初始能量
double energy; // 剩余能量
int nodeId; // 节点ID
// 邻居列表(节点ID)
std::vector<int> neighbors;
// 记录已经转发过的数据包: (srcId, seq) 对
std::set<std::pair<int, int>> forwardedPackets;
// 统计
int packetsGenerated; // 产生的数据包个数(作为源)
int packetsSent; // 发送的数据包个数(包括自己产生的和转发的)
int packetsReceived; // 接收的数据包个数(不包括自己发送的)
// 周期
simtime_t interval; // T=100ms
// 每个节点自己的序列号计数器
int nextSeq;
// 死亡标志
bool dead;
// 记录第一个节点死亡的时间(仿真停止)
static bool firstDeathOccurred;
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
virtual void finish() override;
// 广播数据包
void broadcastPacket(DataPacket *packet);
// 计算距离
double distance(double x1, double y1, double x2, double y2);
// 消耗能量: 发送和接收
void consumeEnergyForTx();
void consumeEnergyForRx();
// 检查能量是否足够发送
bool canSend();
// 检查能量是否足够接收
bool canReceive();
// 安排下一个数据包产生事件
void scheduleNextPacketGeneration();
// 节点死亡处理
void nodeDies();
};
// 静态成员初始化
bool SensorNode::firstDeathOccurred = false;
void SensorNode::initialize() {
// 获取参数
x = par("x");
y = par("y");
radius = par("radius");
isSink = par("isSink");
initialEnergy = par("initialEnergy");
energy = initialEnergy;
interval = par("interval");
nodeId = getIndex(); // 节点ID为模块的索引
// 获取邻居列表:在初始化阶段,所有节点已经创建,我们可以通过遍历所有节点来计算邻居
cModule *network = getParentModule();
for (int i = 0; i < network->getSubmoduleVectorSize(); i++) {
cModule *mod = network->getSubmodule("node", i);
if (mod == this) continue; // 跳过自己
SensorNode *node = dynamic_cast<SensorNode*>(mod);
if (node) {
double d = distance(x, y, node->x, node->y);
if (d <= radius) {
neighbors.push_back(i); // 记录邻居的ID(索引)
}
}
}
// 如果是Sink节点,则不需要产生数据包
if (!isSink) {
// 安排第一个数据包产生事件(在0到interval之间随机时间)
double startTime = uniform(0, interval.dbl());
scheduleAt(simTime() + startTime, new cMessage("generate"));
}
packetsGenerated = 0;
packetsSent = 0;
packetsReceived = 0;
nextSeq = 0;
dead = false;
}
void SensorNode::handleMessage(cMessage *msg) {
if (dead) {
delete msg;
return;
}
if (msg->isSelfMessage()) {
// 产生数据包事件
// 创建数据包
char pktname[40];
sprintf(pktname, "data-%d-%d", nodeId, nextSeq);
DataPacket *pkt = new DataPacket(pktname);
pkt->setSrcId(nodeId);
pkt->setSeq(nextSeq);
pkt->setCreationTime(simTime());
nextSeq++;
packetsGenerated++;
broadcastPacket(pkt);
// 安排下一个周期的事件
scheduleNextPacketGeneration();
delete msg; // 删除自消息
}
else {
// 接收数据包
DataPacket *pkt = check_and_cast<DataPacket*>(msg);
// 消耗接收能量
consumeEnergyForRx();
packetsReceived++;
// 检查是否死亡(在接收后)
if (energy < 0) {
nodeDies();
delete pkt;
return;
}
// 如果是Sink节点
if (isSink) {
// 记录接收到的数据包(需要去重)
// 这里简化:我们用一个全局的map记录,但实际应该放在Sink节点内部
// 注意:我们只记录第一次收到的包
// 假设在Sink节点中,我们有一个map<pair<int,int>, bool>来记录
// 由于我们只在一个Sink节点,可以直接记录
std::pair<int, int> key = std::make_pair(pkt->getSrcId(), pkt->getSeq());
if (receivedPackets.find(key) == receivedPackets.end()) {
receivedPackets.insert(key);
simtime_t delay = simTime() - pkt->getCreationTime();
// 记录延迟和包计数
// ...(可以记录到统计量)
}
// Sink节点不转发
delete pkt;
}
else {
// 普通节点:检查是否已经转发过这个包
std::pair<int, int> key = std::make_pair(pkt->getSrcId(), pkt->getSeq());
if (forwardedPackets.find(key) == forwardedPackets.end()) {
// 记录为已转发
forwardedPackets.insert(key);
// 广播这个包
broadcastPacket(pkt);
}
else {
// 已经转发过,丢弃
delete pkt;
}
}
}
}
void SensorNode::broadcastPacket(DataPacket *pkt) {
if (dead) {
delete pkt;
return;
}
// 消耗发送能量(广播一次)
consumeEnergyForTx();
packetsSent++;
if (energy < 0) {
nodeDies();
delete pkt;
return;
}
// 复制数据包并发送给每个邻居
for (int neighborId : neighbors) {
cModule *mod = getParentModule()->getSubmodule("node", neighborId);
if (mod) {
SensorNode *neighbor = dynamic_cast<SensorNode*>(mod);
if (neighbor && !neighbor->dead) {
// 创建副本
DataPacket *copy = pkt->dup();
// 使用sendDirect直接发送到邻居的gate
sendDirect(copy, 0, 0, neighbor->gate("in"));
}
}
}
delete pkt; // 删除原始包
}
void SensorNode::consumeEnergyForTx() {
double d = radius; // 发送距离按照通信半径(50m)
double l = 4000; // 4000 bits
double Eelec = 50e-9; // 50 nJ/bit -> J/bit
double eamp = 0.0013e-12; // 0.0013 pJ/bit/m4 -> J/bit/m4
double efs = 10e-12; // 10 pJ/bit/m2 -> J/bit/m2
double d0 = 40.0;
double energyConsumed;
if (d < d0) {
energyConsumed = l * Eelec + l * efs * d * d;
}
else {
energyConsumed = l * Eelec + l * eamp * d * d * d * d;
}
energy -= energyConsumed;
}
void SensorNode::consumeEnergyForRx() {
double l = 4000; // 4000 bits
double Eelec = 50e-9; // 50 nJ/bit -> J/bit
double energyConsumed = l * Eelec;
energy -= energyConsumed;
}
void SensorNode::scheduleNextPacketGeneration() {
// 安排下一个周期的事件
double nextTime = simTime().dbl() + interval.dbl() + uniform(0, interval.dbl());
scheduleAt(nextTime, new cMessage("generate"));
}
void SensorNode::nodeDies() {
dead = true;
if (!firstDeathOccurred) {
firstDeathOccurred = true;
// 结束仿真
endSimulation();
}
}
void SensorNode::finish() {
// 在仿真结束时,输出统计信息
recordScalar("generatedPackets", packetsGenerated);
recordScalar("sentPackets", packetsSent);
recordScalar("receivedPackets", packetsReceived);
recordScalar("remainingEnergy", energy);
}
// 计算距离
double SensorNode::distance(double x1, double y1, double x2, double y2) {
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
注意:以上代码省略了Sink节点记录接收包的存储结构(在Sink节点中,我们需要一个receivedPackets成员,类型为set<pair<int,int>>),并且Sink节点不需要产生数据包,所以不会安排自消息。
此外,在广播时,我们假设每个节点都有一个输入门"in",需要在NED文件中定义。
由于洪泛算法效率低,节点会快速死亡。我们设置停止条件为第一个节点死亡,然后输出所有节点的统计信息。
这个实现是简化的,实际中还需要考虑:
- 传输冲突(但题目说不考虑)
- 更精确的能量管理(例如,发送前检查能量是否足够)
- 在广播前,检查邻居是否死亡(在代码中已经检查)
部署节点均匀分布在圆形区域:
在初始化时,节点的位置(x,y)由参数指定。我们需要在网络的initialize()中设置节点的位置?或者由NED文件指定?我们可以在网络初始化时,为每个节点设置随机的(x,y)坐标(在圆内均匀分布)。
在OMNeT++中,我们可以在网络模块的initialize()中设置子模块的参数:
- 网络模块:WirelessNetwork
void WirelessNetwork::initialize() {
// 设置Sink节点在(0,0)
cModule *sink = getSubmodule("sink");
sink->par("x") = 0;
sink->par("y") = 0;
sink->par("isSink") = true;
// 设置其他节点
for (int i=0; i<500; i++) {
cModule *node = getSubmodule("node", i);
if (node) {
double r = 500 * sqrt(uniform(0,1)); // 为了保证均匀分布,半径平方根均匀
double theta = uniform(0,2*PI);
double x = r * cos(theta);
double y = r * sin(theta);
node->par("x") = x;
node->par("y") = y;
node->par("isSink") = false;
}
}
}
注意:均匀分布在圆内:不能直接随机半径(那样会不均匀),应该使用上面的方法(半径按平方根均匀,角度均匀)。
由于代码量很大,以上只是核心代码的框架,实际运行还需要调试。
由于问题要求生成流程图和代码,但流程图无法在此文本中绘制,我们用文字描述流程图:
流程图:
开始
|
初始化网络:部署节点(Sink在圆心,其他节点均匀分布在圆内),计算邻居列表
|
非Sink节点安排第一个数据包产生事件(在[0,T)随机时间)
|
事件循环:
当事件发生时:
如果是自消息(产生数据包):
创建数据包,记录产生时间
广播数据包(发送给所有邻居)
安排下一个周期的产生事件(当前时间+T+随机时间)
如果是接收到数据包:
消耗接收能量,更新能量
如果能量<0,节点死亡,如果是第一个死亡则结束仿真
否则,如果是Sink节点:
如果是新包,记录接收(包计数和延迟)
否则(普通节点):
如果该包没有转发过,则广播
广播数据包时:
消耗发送能量(按照50m计算)
如果能量<0,节点死亡,结束仿真
否则,将数据包发送给所有邻居(邻居如果死亡则跳过)
|
当第一个节点死亡,结束仿真
|
输出统计:每个节点的产生包数、发送包数、接收包数、剩余能量;Sink节点记录的各源节点的包接收个数和延迟
以上就是一个简化的实现方案。由于时间限制,代码可能需要进一步调试和完善。
</think>
### 圆形区域传感器网络路由算法仿真实现(洪泛算法)
#### 流程图
```mermaid
graph TD
A[开始] --> B[初始化网络]
B --> C[部署500个传感器节点]
C --> D[Sink节点位于圆心]
D --> E[计算邻居列表]
E --> F{周期T=100ms到达}
F -->|是| G[随机节点生成数据包]
G --> H[广播数据包]
H --> I[邻居节点接收数据包]
I --> J{是否Sink节点?}
J -->|是| K[记录数据包及延迟]
J -->|否| L{是否新包?}
L -->|是| M[转发广播]
L -->|否| N[丢弃数据包]
M --> I
K --> O{节点能量耗尽?}
O -->|是| P[标记死亡并结束仿真]
O -->|否| F
```
#### 核心逻辑
1. **网络初始化**:
- 在半径500m圆形区域随机均匀部署500个节点
- Sink节点固定于圆心(0,0)
- 每个节点计算通信半径50m内的邻居列表
2. **数据生成阶段**:
- 每100ms周期内,每个节点随机选择时刻生成500字节数据包
- 源节点ID和序列号标识唯一数据包
3. **洪泛路由过程**:
- 节点首次收到数据包时:
- 若非Sink节点:广播转发给所有邻居
- 若为Sink节点:记录(源ID,序列号)和端到端延迟
- 重复数据包直接丢弃
4. **能量消耗模型**:
```python
def calc_energy(distance, l=4000): # l=500字节*8=4000比特
Eelec = 50e-9 # 50nJ/bit
if distance < 40: # 阈值d0=40m
return l*Eelec + l*10e-12*(distance**2) # efs=10pJ/bit/m²
else:
return l*Eelec + l*0.0013e-12*(distance**4) # eamp=0.0013pJ/bit/m⁴
```
5. **终止条件**:
- 当任一节点能量 ≤ 0 时停止仿真
- 统计各节点:产生/发送/接收包数、剩余能量
- Sink节点统计:接收包数及延迟
#### OMNeT++ 核心代码
```cpp
// DataPacket.msg
packet DataPacket {
int srcId;
int seqNum;
simtime_t creationTime;
}
// SensorNode.ned
simple SensorNode {
parameters:
double x @unit("m");
double y @unit("m");
bool isSink = default(false);
gates:
input in;
}
// 核心实现(SensorNode.cc)
void SensorNode::initialize() {
// 初始化位置、邻居列表等
energy = 0.5; // 初始能量
scheduleAt(uniform(0, 0.1), new cMessage("generate")); // 100ms内随机启动
}
void SensorNode::handleMessage(cMessage *msg) {
if (msg->isSelfMessage()) { // 数据生成事件
DataPacket *pkt = new DataPacket("data");
pkt->setSrcId(getId());
pkt->setSeqNum(seqCounter++);
pkt->setCreationTime(simTime());
broadcast(pkt);
scheduleAt(simTime() + 0.1, msg); // 下一周期
} else { // 数据包到达
DataPacket *pkt = check_and_cast<DataPacket*>(msg);
consumeEnergy(RX_ENERGY); // 接收耗能
if (isSink) {
recordPacketReception(pkt); // Sink记录数据
} else if (!isDuplicate(pkt)) {
markAsForwarded(pkt);
broadcast(pkt->dup()); // 洪泛转发
}
delete pkt;
}
}
void SensorNode::broadcast(DataPacket *pkt) {
consumeEnergy(calc_energy(50)); // 按最大距离50m计算发送能耗
for (int neighbor : neighbors) {
sendDirect(pkt->dup(), getParentModule()->getSubmodule("node", neighbor), "in");
}
delete pkt;
}
```
#### 关键参数实现
| 能量消耗类型 | 计算公式 | 值 (J) |
|--------------------|-----------------------------------|----------|
| 发送能量 (d<40m) | 4000*(50e-9) + 4000*10e-12*d² | 0.0002 + 4e-8*d² |
| 发送能量 (d≥40m) | 4000*(50e-9) + 4000*0.0013e-12*d⁴ | 0.0002 + 5.2e-12*d⁴ |
| 接收能量 | 4000*50e-9 | 0.0002 |
#### 输出统计
1. 节点级:
- 产生/发送/接收包数量
- 剩余能量
2. Sink级:
- 各源节点成功接收包数
- 最小/平均/最大端到端延迟