学习自:(6条消息) omnet++ 快速入门 | 计算机网络仿真 | omnet++ 入门教程_叶局长的博客-CSDN博客
1、使用omnet仿真的一般步骤
主要有3步:
- 使用ned(network description)定义网络拓扑结构;
- 利用C++编写实现网络的各种行为;
- 编写配置文件指定网络参数,利用配置文件启动项目;
2、新建项目
File->New Project->Omnet++ Project
项目新建之后,我们可以看到src、simulation文件夹:
simulation:存放项目配置文件;我们在配置文件中指定想要模拟的网络、网络中节点的位置等
src:存放所有源代码,包括ned文件、C++源文件等;
3、定义仿真网络的拓扑结构
使用omnet进行仿真,我们首先需要定义网络的拓扑结构
①新建空network
在src目录下,我们新建一个network,选择one item,此时我们就有了一个网络,当然现在里边什么都没有。点击Design标签,可以以GUI的形式查看ned文件当前的内容。
②Module
1)Module的概念
逻辑上,omnet++中的module相当于C++中的class,并且可以在module中定义属性,我们用module实例化出的module对象,又可以放到其他的模块中。
代码上,omnet++中的module就是C++中的class,在定义网络行为时,我们需要用到它的属性、参数等。
在omnet中,网络中的所有东西(如节点、服务器等)都以module形式定义:
- 最底层的module称为simple module;
- mudule可以添加到compound module中;
- module间可以互相嵌套,没有嵌套的层级限制;
- module间可以定义继承关系;
一个Node、很多个Nodes、一个Network,都可以是一个Module;Network的本质上就是compound module;
2)Module的定义步骤
一个module的定义分为3步:
- 在ned中定义;
- 使用C++继承omnet的cModule或cSimpleModule类,定义一个Module类;
- 将ned文件中定义的Module与C++中定义的Module联系起来;
①ned文件中的定义
1)simple module
定义一个simple module的语法如下:
simple Host { ... parameters://定义该module的参数,如传输速率等 ... gates: //定义该module的输入、输出口及个数 ... }
2)compound module
定义一个compound module的一般语法如下,所有的sections都是可选的:
module Host { types: //定义module类型(在submodules中使用),信道类型(在connections中使用)等 ... parameters://定义该moduel的参数,如传输速率、节点个数等 ... gates: //定义该module的输入和输出口以及个数 ... submodules://定义submodule实例 ... connections://定义submodule间的链接方式 ... }
②C++文件中的定义
对于simple module,我们继承自cSimpleModule;
对于compound module,我们继承自cModule;
我们来定义一个C++ Module类:
#include<omnetpp/csimplemodule.h> class ExampleModule:public omnetpp::cSimpleModule{ public: ExampleModule(); virtual ~ExampleModule(); }; Define_Module(ExampleModule);//Module与其对应class之间的联系
③将C++文件与ned文件联系起来:Define_Module
在类定义的后面,添加Define_Module(module名);,将ned文件中的module与指定的C++文件中的Module类联系起来
③元数据注释(属性)
NED属性是元数据注释,这些属性可以被附加到modules、parameters、gates、connections、NED files、packages和NED中的一切虚拟事物。
@display、@class、@namespace、@unit、@prompt、@loose、@directIn都是属性,这些属性我们已经在之前的几节中说过了,但是那些例子只抓住了表面,给出的是这些属性的基本使用方法。
使用属性,我们就可以为NED属性添加额外的信息。一些属性已经被NED仿真内核给解释了;另一些属性从仿真模型中读入并使用,并且提供给NED编辑工具使用。
将这些属性添加到某个类型中,之后我们在定义每个实例时就不能使用不同的属性了。所有module、connections、parametes等实例,都是由NED文件中的特定位置创建出来的,它们都有其特有的属性。
下面是一个使用元数据注释的例子:
@namespace(foo);//文件属性 module Example { parameters: @node;//module属性 @display("i=device/pc");//module属性 int a @unit(s)=default(1);//parameter属性 gates: output out @loose @labels(pk);//gate属性 submodules: src:Source{ parameters: @display("p=150,100");//submodule属性 count @prompt("Enter count:");//为parameter添加一个属性 gates: out[] @loose;//为gate添加一个属性 } ... connections: src.out++ -->{@display("ls=green,2");}--> sink1.in;//connection属性 src.out++ --> Channel{@display("ls=green,2");}--> sink2.in; }
④信道
在ned文件中,我们可以定义信道,定义的一般语法如下:
channel 信道名 extends 继承的信道
{
...信道属性...
}
大多数情况下,我们不用去定义信道,oomnet++自带了三个信道ned.IdealChannel,ned.DelayChannel和ned.DatarateChannel,详细可以看:https://doc.omnetpp.org/omnetpp/manual/#sec:ned-lang:channels
我们也可以直接在connections中写出信道的属性,省去了定义信道的步骤。
⑤配置文件
定义在parameters中的属性,在配置文件.ini中指定。
4、几个例子
omnet++根目录的sample目录下官方提供了许多例子供我们参考,这里挑选几个比较经典的例子:
AHOLA
tictoc
SimpleNode和Sink
先看一个RIMAC的节点,SimpleNode:
package RIMAC.simplenode; simple SimpleNode { parameters: double x @unit(m); double y @unit(m); double txRange @unit(m);//数据传输距离 double senRange @unit(m);//数据感知距离 double Twait @unit(s);//事件等待时间 double bitRate;//传输速率 double animationHoldTimeOnCollision @unit(s);//碰撞时动画持续时间 volatile double frameTime=uniform(0.5s,1.5s) @unit(s);//一帧的时间 double sleepTime @unit(s);//睡眠时间 double sendConsumption @unit(W);//发送功耗 double recvConsumption @unit(W);//接收功耗 double sleepConsumption @unit(W);//睡眠功耗 double idleConsumption @unit(W);//空闲功耗 double energy @unit(J);//初始能量 int packetSize @unit(B);//数据包大小 @display("p=$x,$y"); @class(RIMAC::simpleNode); @signal[energyLeft](type="double"); @staticstic[energyLeftStat](title="energyLeft";source="energyLeft";record=vector,stats); gates: input in @directIn; }
Sink定义:
package RIMAC; import RIMAC.simplenode.SimpleNode; simple Sink extends SimpleNode { parameters: @class(RIMAC::Sink); @display("p=$x,$y;i=device/terminal;r=$txRange"); @signal[e2etd](type="double"); @statistic[e2etdStat](title="e2etd";source="e2etd";record=vector,stats); @signal[ae2etd](type="double"); @statistic[ae2etdStat](title="ae2etd";source="ae2etd";record=vector,stats); }
5、控制网络仿真时的行为
①omnet仿真原理
1)离散事件模拟
- 离散事件系统(Discrete Event System)是指事件发生在时间线中离散的部分,对于计算机网络而言正像是如此;
- 离散事件模拟系统通过在称为FES(Future Event Set)或FEL(Future Event List)的数据结构中保存未来事件的集合来实现;
- omnet++底层使用二叉堆实现的优先级队列和事件循环(Eventloop)来实现这一套模拟机制。
2)omnet事件循环
启动仿真后,事件执行的伪代码如下:
初始化(initialize) //包括构建模型,添加初始化事件到FES中 while(FES不为空&&仿真未结束) { 从FES中取出事件 t := 该事件发生时间 执行事件 (事件执行过程中,可能往FES中添加事件,也可能往FES中删除事件) } 结束仿真(写入统计数据,etc.)
②网络的初始化和结束原理
omnet网络的整个过程如下:
perform simulation run: build network (i.e. the system module and its submodules recursively) insert starter messages for all submodules using activity() do callInitialize() on system module enter event loop if (event loop terminated normally) do callFinish() on system module clean up 模拟运行: 构造网络 (递归构造系统module和每个module的submodule) 用activity()为所有submodule插入开始消息 在系统module中运行callInitialize(): 进入事件循环 if(事件循环正常结束) 在系统mudule中运行callFinish() 清理
其中callInitialize()和callFinish()的伪代码如下:
callInitialize() { call to user-defined initialize() function if (module is compound) for (each submodule) do callInitialize() on submodule } { 调用用户自定义的initialize()函数 if (module是compound module) for(each submodule) 在每个submodule中调用callInitialize() } callFinish() { if(module is compound) for(each submodule) do callFinish() on submodule call to user-defined finish() function }
1)模块的初始化
从上边的执行过程可知,module是有初始化事件的,我们通过父类cSimpleModule的initialize()方法来进行初始化。
2)模块的多阶段初始化
可以重写两个方法来进行多阶段的初始化,在numInitStages中返回阶段的个数,一般来说,我们可以在第一个初始化阶段进行变量赋值等操作,后面的阶段可以进行周期计算等:
virtual void initialize(int stage); virtual int numInitStages() const;
3)模块的结束
模块通过调用finish()来进行仿真结束的工作
③消息的发送与接收
消息的发送由sendXXX系列函数完成,消息的接收基本都由handleMessage完成
1)默认的消息cMessage类
cMessage类是所有消息类的父类,我们可以自定义一个消息类(继承自cMesaage),也可以直接使用这个类:
cMessage(const char *name=nullptr , short kind=0)
2)sendXXX()
Message sending. virtual void send (cMessage *msg, int gateid) virtual void send (cMessage *msg, const char *gatename, int gateindex=-1) virtual void send (cMessage *msg, cGate *outputgate) virtual void sendDelayed (cMessage *msg, simtime_t delay, int gateid) virtual void sendDelayed (cMessage *msg, simtime_t delay, const char *gatename, int gateindex=-1) virtual void sendDelayed (cMessage *msg, simtime_t delay, cGate *outputgate) virtual void sendDirect (cMessage *msg, cModule *mod, const char *inputGateName, int gateIndex=-1) virtual void sendDirect (cMessage *msg, cModule *mod, int inputGateId) virtual void sendDirect (cMessage *msg, cGate *inputGate) virtual void sendDirect (cMessage *msg, simtime_t propagationDelay, simtime_t duration, cModule *mod, const char *inputGateName, int gateIndex=-1) virtual void sendDirect (cMessage *msg, simtime_t propagationDelay, simtime_t duration, cModule *mod, int inputGateId) virtual void sendDirect (cMessage *msg, simtime_t propagationDelay, simtime_t duration, cGate *inputGate)
3)handleMessage(Msg *)
4)ScheduleAt():自消息的发送
④仿真的主要参考资料
- 当我们不知道一个函数的作用时,绝大多数情况下,都是去查cSimpleModule类:cSimpleModule
- 其次会去SimulationManual中去查找,其中会有一些实际应用的例子