文档链接 https://docs.omnetpp.org/tutorials/tictoc/part3/
TicToc1
tictoc1.ned
// 简单模块,包含两个端口,一个输入,一个输出
simple Txc1{
gates:
input in;
output out;
}
// 网络,包含两个子模块,这两个子模块都是Txc1的实例
// 通过connections定义子模块之间的连接关系
network Tictoc1{
submodules:
tic: Txc1;
toc: Txc1;
connections:
tic.out --> {delay = 100ms;} --> toc.in;
toc.out --> {delay = 100ms;} --> tic.in;
}
txc1.cc
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Txc1 : public cSimpleModule{
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
// 所有普通模块都要调用这个函数进行宏注册
Define_Module(Txc1);
// 初始化函数,在仿真开始运行一次,这里实现发送第一条消息
void Txc1::initialize(){
// getName获取实例名称,也就是ned模块中实例名称
// 字符串比较,tic发送第一条消息
if(strcmp("tic", getName()) == 0){
// 创建消息,并通过该模块的out端口发送出去,字符串就是消息名称
cMessage *msg = new cMessage("tictocMsg");
send(msg, "out");
}
}
// 当该类的对象接收到一个消息时开始执行handleMessage函数
void Txc1::handleMessage(cMessage *msg){
// 任何一个模块收到消息之后再经过该模块的out端口发送出去
send(msg, "out");
}
总结
所有simple模块都要继承cSimpleModule
类,并重initialize()
和handleMessage()
两个函数。
initialize()
,初始化函数,在整个程序开始运行时执行一次。
handleMessage()
,每次有消息到达该模块时,都会执行一次该函数。
message
和events
都由cMessage
对象表示
TicToc2
tictoc2.ned
这里主要是对ned模块中的设计样式进行了修改,基本连接关系不变
simple Txc2
{
parameters:
//给模块加上图片,直接对应安装目录image/block中的图片
@display("i=block/routing");
gates:
input in;
output out;
}
network Tictoc2
{
// 绘制背景大小
@display("bgb=300,300");
submodules:
tic: Txc2 {
parameters:
// 修改icon的颜色,设置网络中结点的位置
@display("i=,cyan;p=100,100");
}
toc: Txc2 {
parameters:
@display("i=,gold;p=200,200");
}
connections:
tic.out --> { delay = 100ms; } --> toc.in;
tic.in <-- { delay = 100ms; } <-- toc.out;
}
txc2.cc
增加了信息打印,逻辑功能没有变化
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Txc2 : public cSimpleModule{
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Txc2);
void Txc2::initialize(){
if(strcmp("tic", getName()) == 0){
// EV相当于C++中的cout
// 可以使用std::cout<< <<endl;打印在控制台
EV << "Sending initial message\n";
cMessage *msg = new cMessage("tictocMsg");
send(msg, "out");
}
}
void Txc2::handleMessage(cMessage *msg){
// msg->getName()消息的名字,在这里就是tictocMsg
EV << "Received message `" << msg->getName() << "', sending it out again\n";
send(msg, "out");
}
TicToc3
该案例TicToc2
的基础上增加状态变量counter
,每接收一次消息计数器减一,减到0之后消息删除,事件结束。
并增加观测事件WATCH(counter)
。
txc3.cc
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Txc3 : public cSimpleModule{
private:
int counter; // 初始化私有变量
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Txc3);
void Txc3::initialize(){
// 赋值并观测
counter = 10;
WATCH(counter);
if(strcmp("tic", getName()) == 0){
EV << "Sending initial message\n";
cMessage *msg = new cMessage("tictocMsg");
send(msg, "out");
}
}
void Txc3::handleMessage(cMessage *msg){
counter--;
// getName()打印出tic/toc
// 计数器为0时,消息删除,仿真结束
if(counter == 0){
EV<<getName()<<"'s counter reached zero, deleting message\n";
delete msg;
} else{
EV << getName() <<"'s counter is " << counter << ", sending back message\n";
send(msg, "out");
}
}
TicToc4
本案例主要研究了在模块中添加参数、初始化参数以及cc文件与ned文件传参三个问题。
tictoc4.ned
simple Txc4{
parameters:
// 定义第一个变量,决定模块是否发送第一个消息,默认false
bool sendMsgOnInit = default(false);
// 定义接收消息限制值,默认设置为2
// 同时也可以在ini文件中进行初始化
int limit = default(2);
@display("i=block/routing");
gates:
input in;
output out;
}
network Tictoc4{
submodules:
tic: Txc4{
parameters:
sendMsgOnInit = true; // 初始化发送第一个消息
@display("i=,blue;p=100,100");
}
toc: Txc4{
parameters:
sendMsgOnInit = false;
@display("i=,yellow;p=200,200");
}
connections:
tic.out --> {delay = 100ms;} --> toc.in;
toc.out --> {delay = 100ms;} --> tic.in;
}
txc4.cc
#include <string.h>
#include <omnetpp.h>
#include <stdio.h>
using namespace omnetpp;
class Txc4 : public cSimpleModule{
private:
int counter;
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Txc4);
void Txc4::initialize(){
// counter不再直接赋值,而是通过par()进行传参
// par()括号中的内容是ned模块中定义的变量,以字符串形式表示
counter = par("limit");
//
if(par("sendMsgOnInit").boolValue() == true){
EV << "Sending initial message\n";
cMessage *msg = new cMessage("tictocMsg");
send(msg, "out");
}
}
void Txc4::handleMessage(cMessage *msg){
counter--;
if(counter == 0){
EV << getName() <<"'s counter reached zero, deleting message\n";
delete msg;
} else{
EV << getName() <<"'s counter is " << counter << ", sending back message\n";
send(msg, "out");
}
}
关于赋值的总结
在模块中定义的变量总共有三种赋值方式,第一种是默认值,即使用default()
,第二种是在生成实例时进行赋值,例如tictoc4.ned
中的sendMsgOnInit = true
,第三种是omnetpp.ini
文件中进行赋值。三种赋值方式的优先级为第二种>第三种>第一种。
omnetpp.ini
:
Tictoc4.tic.limit = 3
Tictoc4.toc.limit = 5
// ini文件支持通配符,因此如果两个值赋值相同,可以改写成
Tictoc4.*.limit = 4
// 或者
Tictoc4.t*c.limit = 4
// 甚至可以是
**.limit = 4
TicToc5
在案例tictoc4
的基础上使用了ned继承方式
tictoc.ned
simple Txc5{
parameters:
bool sendMsgOnInit = default(false);
int limit = default(2);
@display("i=block/routing");
gates:
input in;
output out;
}
// Tic5和Txc5作为Txc5的派生,分别进行定义并进行参数赋值
simple Tic5 extends Txc5{
parameters:
sendMsgOnInit = true;
@display("i=,green;p=200,200");
}
simple Toc5 extends Txc5{
parameters:
sendMsgOnInit = false;
@display("i=,orange;p=100,100");
}
network Tictoc5{
submodules:
tic: Tic5;
toc: Toc5;
connections:
tic.in <-- {delay=100ms;} <-- toc.out;
toc.in <-- {delay=100ms;} <-- tic.out;
}
txc5.cc
逻辑功能与txc4.cc
相同
TicToc6
案例tictoc6
在于实现消息的延迟发送,即一个模块在接收到消息之后,延迟一定时长再发送给另一个模块。
在OMNet++中这样的延时是通过模块将消息发送给自身实现的。
tictoc6.ned文件参照tictoc2.ned
txc6.cc
#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Txc6 : public cSimpleModule{
private:
cMessage *event; //指向用来计时的对象
cMessage *tictocMsg; //记录收到的消息,直到将其发回
public:
Txc6(); //构造函数,在txc6的基础上构造函数
virtual ~Txc6(); //析构函数,把构造的函数删除
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Txc6);
Txc6::Txc6(){
// 初始化为空指针
event = tictocMsg = nullptr;
}
Txc6::~Txc6(){
//删除已分配的自消息
cancelAndDelete(event); //把自消息event先取消再删除
delete tictocMsg; //直接删除
}
void Txc6::initialize(){
event = new cMessage("event");
tictocMsg = nullptr; //初始化实际传输的对象、
// 这里没有直接将消息发送出去,而是创建消息之后启用定时器,定时5s
// scheduleAt(simtime_t t, cMessage *msg),指定时一段时间之后发送自消息
if(strcmp("tic", getName()) == 0){
EV <<"Scheduling first send to t=5.0s\n";
tictocMsg = new cMessage("tictocMsg");
scheduleAt(5.0, event);
}
}
// 5s tic收到自消息,然后将tictocMsg发送出去
// 5.1s toc收到tictocMsg!=event,执行else,保存收到的消息,定时1s,发送自消息
// 6.1s toc收到自消息 = event,将之前保存的消息发送出去,并清除消息
// 6.2s tic收到tictocMsg!=event,执行else,保存收到的消息,定时1s,发送自消息
void Txc6::handleMessage(cMessage *msg){
if(msg == event){
EV << "Wait period is over, sending back message\n";
send(tictocMsg, "out");
tictocMsg = nullptr;
}else{
EV <<"Message arrived, starting to wait 1 sec...\n";
tictocMsg = msg;
// simTime()当前仿真时间
scheduleAt(simTime() + 1.0, event);
}
}
TicToc7
案例tictoc7
在前一个案例的基础上将延时时间由固定值1s修改为一个可以由ini文件设置的任意可变值,同时增加了丢包率的设计,当丢包发生时,消息删除,仿真结束
tictoc7.ned
simple Txc7
{
parameters:
@display("i=block/routing");
//volatile可变的,@unit(s)属性修饰指明当前变量的单位,此处s表示单位为秒
volatile double delayTime @unit(s); //设置延迟发送的时间变量
gates:
input in;
output out;
}
network Tictoc7
{
@display("bgb=300,300");
submodules:
tic: Txc7 {
parameters:
@display("i=,green;p=200,200");
}
toc: Txc7 {
parameters:
@display("i=,red;p=100,100");
}
connections:
tic.out --> { delay = 100ms; } --> toc.in;
toc.out --> { delay = 100ms; } --> tic.in;
}
txc7.cc
#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Txc7 : public cSimpleModule{
private:
cMessage *event;
cMessage *tictocMsg;
public:
Txc7();
virtual ~Txc7();
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Txc7);
Txc7::Txc7(){
event = tictocMsg = nullptr;
}
Txc7::~Txc7(){
cancelAndDelete(event);
delete tictocMsg;
}
void Txc7::initialize(){
event = new cMessage("event");
tictocMsg = nullptr;
if(strcmp("tic", getName()) == 0){
EV <<"Scheduling first send to t=5.0s\n";
scheduleAt(5.0, event);
tictocMsg = new cMessage("tictocMsg");
}
}
void Txc7::handleMessage(cMessage *msg){
double p;
if(msg == event){
EV <<"Wait period is over, sending back message\n";
send(tictocMsg, "out");
tictocMsg = nullptr;
}else{
// 设置丢包率,0~1之间的一个随机数
p = uniform(0,1);
EV << "p = "<< p <<"\n";
if(p < 0.1){
EV <<"\"Losing\" message\n";
delete msg;
}
else{
// 获取ned模块中delayTime参数的值
simtime_t delay = par("delayTime");
EV <<"Message arrived, starting to wait " << delay <<" sec...\n";
tictocMsg = msg;
scheduleAt(simTime()+delay, event);
}
}
}
omnetpp.ini
文件中增加:
[Config Tictoc7]
network = Tictoc7
// argument to exponential() is the mean; truncnormal() returns values from
// the normal distribution truncated to nonnegative values
Tictoc7.tic.delayTime = exponential(3s)
Tictoc7.toc.delayTime = truncnormal(3s,1s)
如果整个程序多运行几次会发现丢包时间总是在第14次发生,这是因为这个仿真软件的随机事件实际上是确定性算法,可以在ini文件中修改种子信息,测试得到不同结果:
[General]
seed-0-mt= 8 //or any other 32-bit value
TicToc8
案例tictoc8
在前一个案例的基础上,设计了tic
的重传功能,tic
每次发送消息之后,会开启定时,定时完成意味着信息丢失,tic
会启动重传机制。
在该案例中,tic
和toc
的处理函数逻辑不同,因此应该分开定义。
tictou8.ned
simple Tic8{
parameters:
@display("i=block/routing");
gates:
input in;
output out;
}
simple Toc8{
parameters:
@display("i=block/process");
gates:
input in;
output out;
}
network Tictoc8{
submodules:
tic: Tic8 {
parameters:
@display("i=,green;p=200,200");
}
toc: Toc8 {
parameters:
@display("i=,red;p=100,100");
}
connections:
tic.out --> { delay = 100ms; } --> toc.in;
toc.out --> { delay = 100ms; } --> tic.in;
}
txc8.cc
#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Tic8 : public cSimpleModule{
private:
simtime_t timeout; // 定义超时时间
cMessage *timeoutEvent; // 定义超时消息事件
public:
Tic8();
virtual ~Tic8();
protected:
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Tic8);
Tic8::Tic8(){
timeoutEvent = nullptr;
}
Tic8::~Tic8(){
cancelAndDelete(timeoutEvent);
}
void Tic8::initialize(){
// 初始化变量
timeout = 1.0;
timeoutEvent = new cMessage("timeoutEvent");
EV << "Sending initial message\n";
cMessage *msg = new cMessage("tictocMsg");
//tic8->toc8
send(msg, "out");
//消息发送完成后,timeout时间后判断消息有没有接受到
scheduleAt(simTime()+timeout, timeoutEvent);
}
void Tic8::handleMessage(cMessage *msg){
if(msg == timeoutEvent){
// 如果能收到超时事件消息,证明消息丢失,需要重新传输
EV << "Timeout expired, resending message and restarting timer\n";
cMessage *newMsg = new cMessage("tictocMsg");
send(newMsg, "out");
scheduleAt(simTime()+timeout, timeoutEvent);
}else {
// 没有收到超时事件消息,收到的是正常消息,取消超时事件消息
EV << "Timer cancelled.\n";
cancelEvent(timeoutEvent);
delete msg;
// 重新发送一个消息给toc8,并重新开始超时定时
cMessage *newMsg = new cMessage("tictocMsg");
send(newMsg, "out");
scheduleAt(simTime()+timeout, timeoutEvent);
}
}
class Toc8 : public cSimpleModule{
protected:
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Toc8);
void Toc8::handleMessage(cMessage *msg)
{
if (uniform(0, 1) < 0.1) {
EV << "\"Losing\" message.\n";
// 丢包之后,使用bubble()函数在仿真界面给用户提示
bubble("message lost");
delete msg;
}
else {
EV << "Sending back same message as acknowledgement.\n";
// toc8->tic8
send(msg, "out");
}
}
TicToc9
不同于案例tictoc8
中每次消息重传都要重新创建消息,tictoc9
中重传的消息是拷贝的一份副本,当tic
收到toc
发回的应答信息时,才删除原始数据信息。
tictoc9.ned文件参照tictoc8.ned
txc9.cc
#include <stdio.h>
#include <string.h>
#include <omnetpp.h>
using namespace omnetpp;
class Tic9 : public cSimpleModule{
private:
simtime_t timeout;
cMessage *timeoutEvent;
int seq; // 采用序列号进行验证
cMessage *message;
public:
Tic9();
virtual ~Tic9();
protected:
//为了避免handleMessage()过长,创建了两个新的函数
virtual cMessage *generateNewMessage(); // 生成新的消息
virtual void sendCopyOf(cMessage *msg); // 发送拷贝
virtual void initialize() override;
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Tic9);
Tic9::Tic9()
{
timeoutEvent = message = nullptr;
}
Tic9::~Tic9()
{
cancelAndDelete(timeoutEvent);
delete message;
}
void Tic9::initialize(){
seq = 0;
timeout = 1.0;
timeoutEvent = new cMessage("timeoutEvent");
EV << "Sending initial message.\n";
// 调用新的函数生成事件消息
message = generateNewMessage();
// 拷贝副本并发送副本
sendCopyOf(message);
// 启动定时
scheduleAt(simTime()+timeout, timeoutEvent);
}
void Tic9::handleMessage(cMessage *msg){
// 超时,拷贝原始消息并发送副本
if(msg == timeoutEvent){
EV << "Timeout expired, resending message and restarting timer\n";
sendCopyOf(message);
scheduleAt(simTime()+timeout, timeoutEvent);
} else{
//收到应答消息"ack"
EV << "Received: " << msg->getName() << "\n";
delete msg;
//删除定时,并删除原始消息
EV << "Timer cancelled.\n";
cancelEvent(timeoutEvent);
delete message;
// 重新发送新的消息
message = generateNewMessage();
sendCopyOf(message);
scheduleAt(simTime()+timeout, timeoutEvent);
}
}
cMessage *Tic9::generateNewMessage(){
char msgname[20];
// 利用seq记录当前发送的消息是第几条消息
sprintf(msgname, "tic-%d", ++seq);
cMessage *msg = new cMessage(msgname);
return msg;
}
void Tic9::sendCopyOf(cMessage *msg){
// 发送拷贝副本
// dup函数还可用于多播或广播
cMessage *copyMsg = (cMessage *)msg->dup();
send(copyMsg,"out");
}
class Toc9 : public cSimpleModule{
protected:
virtual void handleMessage(cMessage *msg) override;
};
Define_Module(Toc9);
void Toc9::handleMessage(cMessage *msg){
if(uniform(0,1) < 0.1){
EV << "\"Losing\" message.\n";
bubble("message lost");
delete msg;
}else{
EV << msg << " received, sending back an acknowledgement.\n";
delete msg;
// 成功收到消息,发送一个ack进行响应
send(new cMessage("ack"), "out");
}
}