tictoc tutorial for omnet++ -...
TRANSCRIPT
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 1 –
TicToc Tutorial for OMNeT++
4.07
这是个简短的教程,通过一个建模和仿真的实例来引导你入门 OMNET++,同时向你介
绍一些广泛使用的 OMNET++特性。
本教程基于一个简单的 Tictoc 仿真样例,该样例保存在 OMNET++安装目录下的
sample/tictoc 子目录,所以你现在就可以试着让这个样例运行,但如果你跟着下面的步骤
一步一步来的话,将会收获更多。
请注意请注意请注意请注意::::我们假设你已经安装了 OMNET++并且运行良好,同时认为你具备一定的 C++基
础,大体熟悉操作系统上的 C/C++开发环境(编辑源代码、编译和调试等)。(后两者不是
我们关心的范畴,网上有很多优秀的书籍、教程,你可以自己去充电)。我们极力推荐使用
OMNeT++集成开发环境来编辑、建立仿真。
为了让读者更容易学习此样例,这里所有的源代码都交叉链接到 OMNET++ API 文档。
本文档及其 Tic Toc模型是对 Ahmet Sekercioglu(Monash University)的原始 Tic Toc教
程的一个扩展版本。
目录目录目录目录
1. 扬帆启航
2. 改进型的两节点 TicToc
3. 模拟真实网络
4. 增加统计收集
5. 通过 Plove和 Scalars进行结果可视化
一一一一、、、、 扬帆起航扬帆起航扬帆起航扬帆起航
OMNeT++普遍被用来电信网络的仿真,由此开始我们的主题。首先,让我们来考虑一
个包含两个节点的“网络”,节点所做的事非常简单就是:一个节点创建数据包,然后这两
个节点将该数据包来回传送,(译者注:就像打乒乓球一样),我们把这两个节点分别称之为
“tic”和“toc”。
从零开始——实现你的第一个仿真的步骤:
1. 创建一个工作目录,取名 tictoc,并切换到该目录。(译者注:路径名不能含空格和
中文字符)。
2. 创建一个拓扑文件来描述该样例网络。拓扑文件是标识网络节点及其链路的文本文
件,你可以用你喜欢的任意编辑器均可。我们对它取名为 tictoc1.ned:
// This file is part of an OMNeT++/OMNEST simulatio n example.
// Copyright (C) 2003 Ahmet Sekercioglu
// Copyright (C) 2003-2008 Andras Varga
// This file is distributed WITHOUT ANY WARRANTY. S ee the file
// `license' for details on this and other legal ma tters.
//
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 2 –
simple Txc1
{
gates:
input in;
output out;
}
//
// Two instances (tic and toc) of Txc1 connected bo th ways.
// Tic and toc will pass messages to one another.
//
network Tictoc1
{
submodules:
tic: Txc1 ;
toc: Txc1 ;
connections:
tic.out --> { delay = 100ms; } --> toc.in;
tic.in <-- { delay = 100ms; } <-- toc.out;
}
最好从下往上看,意思如下:
� Tictoc1是一个包含两个子模块的网络,tic 和 toc。这两个模块都是一个称做 Txc1
的简单模块类型的实例。我们把 tic 的输出门(out)连到 toc的输入门(in),反过来也
一样( network … {…} )。每个链路上会有 100ms的传播延迟。
� Txc1是一个简单模块类型(这意味着是 NED 描述的一个基本单位,在 NEDlevel上
是具有原子性的,并通过 C++实现)。Txc1有一个叫做 in 的输入门和一个叫做 out
的输出门。(simple … {…} )。
3. 现在我们需要实现简单模块 Txc1 的功能,通过写一个 C++文件(txc1.cc)来完成。
(译者注:Windows下为 cpp文件 )如下:
//
// This file is part of an OMNeT++/OMNEST simulatio n example.
//
// Copyright (C) 2003 Ahmet Sekercioglu
// Copyright (C) 2003-2008 Andras Varga
//
// This file is distributed WITHOUT ANY WARRANTY. S ee the file
// `license' for details on this and other legal ma tters.
//
#include <string.h>
#include <omnetpp.h>
class Txc1 : public cSimpleModule
{
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 3 –
protected :
// The following redefined virtual function holds t he algorithm.
virtual void initialize();
virtual void handleMessage( cMessage *msg);
};
// The module class needs to be registered with OMN eT++
Define_Module ( Txc1 );
void Txc1::initialize ()
{
// Initialize is called at the beginning of the sim ulation.
// To bootstrap the tic-toc-tic-toc process, one of the modules needs
// to send the first message. Let this be `tic'.
// Am I Tic or Toc?
if (strcmp( "tic" , getName ()) == 0)
{
// create and send first message on gate "out". "ti ctocMsg" is an
// arbitrary string which will be the name of the m essage object.
cMessage *msg = new cMessage ( "tictocMsg" );
send (msg, "out" );
}
}
void Txc1::handleMessage ( cMessage *msg)
{
// The handleMessage() method is called whenever a message arrives
// at the module. Here, we just send it to the othe r module, through
// gate `out'. Because both `tic' and `toc' does th e same, the message
// will bounce between the two.
send (msg, "out" );
}
名为 Txc1的 C++类描述了 Txc1简单模块类型,该类是 cSimpleModule的一个子类,并
通过 Define_Module()宏向 OMNeT++注册。我们对其中的 initialize()和 handleMessage()函数
进行重载。这两个函数将被仿真内核调用:其中初始化函数 initialize()只会被调用一次,而
handleMessage()则在每次消息到达模块时被调用。
在初始化函数中我们创建了一个消息对象(cMessage)并通过输出门 out对外发送。因为
该门连接到其他模块的输入门,所以仿真内核将会把 handleMessage()函数参数中携带的这
个消息传递给其他模块。该消息将会在 NED文件中指派给链路的 100ms传播延迟后到达。
其他模块收到后同样将该消息送回(仍然经过 100ms的延迟),所以整个过程就像一个永不停
歇的乒乓球一样。
在 OMNeT++中,所有的消息(数据包、帧、任务等)和事件(定时、超时)都通过 cMessage
对象(或它的子类)描述。一旦它们被送出或者被调度,就会被仿真内核放在”已调度事件”和”
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 4 –
未来事件”列表中,直到时钟来临,再通过 handleMessage()传递到相关模块。
要注意到在本仿真样例中,并没有内建停止条件,也就是说,仿真会一直进行下去。
你可以从 GUI 图形界面停止它。(你也可以在配置文件里指定一个仿真时间限制或者 CPU
时间限制,但在本教程里我们并没有这样做)。
4. 现在我们来创建一个 Makefile来帮助我们对程序进行编译和链接,并创建一个可执
行的 tictoc:
$ opp_makemake
这条命令将会在 tictoc工作目录下产生一个 Makefile文件。
提示提示提示提示::::对于 Windows+MSVC的用户,命令是 opp_nmakemake,它将创建 Makefile.vc。
5.现在我们来编译和链接我们的第一个仿真实例,运行如下命令:
$ make
提示提示提示提示::::对于 Windows+MSVC的用户,键入 nmake –f Makefile.vc。如果提示说 nmake命
令既非内部命令也非外部命令…,那你需要在 MSVC 目录下找到 vcvars32.bat,然后在你想
要编译之前运行它。
6. 如果你现在就运行编译之后产生的可执行程序,将会提示找不到 omnetpp.ini配置文
件,所以你需要手动建立一个。omnetpp.ini将告诉仿真程序你想要仿真何种网络(同个仿真
程序可以支持同时存有几个网络),你可以将参数传递给模块、显式指定随机数产生器的种
子等。
创建如下非常简单的 omnetpp.ini:
[General]
network = tictoc1
在 tictoc2及其之后的步骤里,omnetpp.ini的文件都是这样的:
# This file is shared by all tictoc simulations.
# Lines beginning with `#' are comments
[General]
# nothing here
[Config Tictoc1]
network = Tictoc1
[Config Tictoc2]
network = Tictoc2
[Config Tictoc3]
network = Tictoc3
[Config Tictoc4]
network = Tictoc4
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 5 –
Tictoc4.toc.limit = 5
[Config Tictoc5]
network = Tictoc5
**.limit = 5
[Config Tictoc6]
network = Tictoc6
[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)
[Config Tictoc8]
network = Tictoc8
[Config Tictoc9]
network = Tictoc9
[Config Tictoc10]
network = Tictoc10
[Config Tictoc11]
network = Tictoc11
[Config Tictoc12]
network = Tictoc12
[Config Tictoc13]
network = Tictoc13
[Config Tictoc14]
network = Tictoc14
[Config Tictoc15]
network = Tictoc15
record-eventlog = true
7. 一旦你完成了上述步骤,你可以通过执行如下命令来运行仿真:
$ ./tictoc
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 6 –
不出意外的话,你就可以见到 OMNeT++仿真窗口了。
提示提示提示提示::::在 Windows 下,直接在目录下运行 tictoc。
8. 点击工具栏的“运行”按钮就可以开始仿真,你将看到 tic 和 toc 彼此之间在相互交
换消息。
主窗口的工具栏显示的是仿真时间,这是个虚拟时间,和程序运行的真实时间(你家墙钟
记录下的)没有任何关系。事实上,这里显示的仿真时间所对应的物理世界时间的多少更多
依赖于你的硬件条件,而不是仿真模型本身的性质和复杂度。
提示到在这里的节点本地处理消息时间为零仿真时间,该模型中,仅仅只有链路传播延
迟能产生仿真时间。
9. 在仿真图形窗口的顶部(右上角),有一个滑动按钮,你可以通过它来控制仿真速度;
按 F8停止仿真(相当于按下工具栏的 STOP按钮),F4是单步执行模式,F5是连续带动画模
式,F6是连续无动画模式,F7是极速模式,完全关闭跟踪特性来追求最大速度。注意到状
态栏上的 event/sec和 simsec/sec的量度不同(译者注:event/sec是每秒处理的事件数,而
simsec/sec是每秒所经历的仿真时间秒数,在本例中,每 100ms产生一个事件,故前者刚好
是后者的 10倍)。
10. 点击关闭图标或者选择”File” -> “Exit” 来退出仿真程序。
源文件源文件源文件源文件: tictoc1.ned, txc1.cc, omnetpp.ini
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 7 –
二二二二、、、、 改进型的两节点改进型的两节点改进型的两节点改进型的两节点 tictoc
1::::增强图形显示增强图形显示增强图形显示增强图形显示,,,,添加调试输出添加调试输出添加调试输出添加调试输出
在这里,我们将做一些工作使得模型在 GUI 界面里更好看一些,选择”images/block”目
录下的 routing.png文件作为 tic 和 toc的图标,分别涂上青色和黄色。这个工作通过在 NED
文件里增加显示代码行。”i=block/routing”代码行指示了图标文件所在的位置。
// "block/routing" icon to the simple module. All submodules of type
// Txc2 will use this icon by default
simple Txc2
{
parameters:
@display("i=block/routing"); // add a default icon
gates:
input in;
output out;
}
// Make the two module look a bit different with colorization effect.
// Use cyan for `tic', and yellow for `toc'.
network Tictoc2
{
submodules:
tic: Txc2 {
parameters:
@display("i=,cyan"); // do not change the icon (first arg of i=) just colorize it
}
toc: Txc2 {
parameters:
@display("i=,gold"); // here too
}
connections:
效果如下:
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 8 –
我们还可以通过在 C++文件中给 Txc1增加一些调试消息,输出到 OMNeT++的 EV 对象
里。
EV << "Sending initial message\n";
还有:
EV << "Received message `" << msg->getName() << "', sending it out again\n";
这样,当你在 OMNeT++的 Tkenv图形界面运行仿真的时候,主文本窗口将会出现以下
输出:
也可以在节点图标上点右键,在菜单上选择“Module output”来单独打开输出窗口,当
你模型比较大的时候(将会有大量快速的输出信息),该功能将变得很有用,你只需要对你感
兴趣的指定模块的日志消息进行输出。
源文件: tictoc2.ned, txc2.cc, omnetpp.ini
2. 增加状态变量增加状态变量增加状态变量增加状态变量
本小节中,我们向模块添加一个计数器,并在十次的消息交换后删除消息。
把计数器作为一个类的成员:
class Txc3 : public cSimpleModule
{
private:
int counter; // Note the counter here
protected:
初始化变量为 10,并且在经过一次的 handleMessage()处理后减 1,当它变为 0后,仿真
程序完成所有的事件处理并终止运行。
注意源代码中的这一行:
WATCH(counter);
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 9 –
这一行可以让我们在 Tkenv图形界面观察 counter的值。在 tic 的图标上双击,在弹出的
窗口里选择”Contents”标签:
继续运行仿真,你可以看到计数器的值不断下降,直到为 0。
源文件: tictoc3.ned, txc3.cc, omnetpp.ini
3. 增加参数增加参数增加参数增加参数
本小节里,你将会学会如何在仿真里添加输入参数:我们将把“神奇的数字”10作为一
个参数并增加一个布尔参数来决定模块是否应该在初始化代码时发送它的第一条消息。(不
管它是一个 tic 还是 toc模块)。
需要在 NED 文件里声明模块参数,数据类型可以是数字、字符串、布尔型或者 xml。
simple Txc4
{
parameters:
bool sendMsgOnInit = default(false); // whether the module should send out a message on
initialization
int limit = default(2); // another parameter with a default value
@display("i=block/routing");
gates:
同时我们也需要修改 C++代码,来读入初始化函数中的参数,并赋予计数器。
counter = par("limit" );
通过第二个参数来决定是否发送该消息:
if (par("sendMsgOnInit").boolValue() == true)
现在,我们可以在 NED 文件或者 omnetpp.ini文件里对参数进行赋值,前者优先于后者。
你也可以在 NED 文件里使用 default(…)给参数定义一个默认值。在这种情况下,你既可以
在 omnetpp.ini里设置参数值,也可以使用 NED 文件提供的默认值。
这里,我们在 NED文件里对一个参数进行赋值:
network Tictoc4
{
submodules:
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 10 –
tic: Txc4 {
parameters:
sendMsgOnInit = true;
@display("i=,cyan");
}
toc: Txc4 {
parameters:
sendMsgOnInit = false;
@display("i=,gold");
}
connections:
在 omnetpp.ini文件里给其他参数赋值:
Tictoc4.toc.limit = 5
因为 omnetpp.ini支持通配符,而且在 NED 里赋值的优先级要比在 omnetpp.ini里要高,下
面几种赋值方式效果是一样的:(*和** 的区别是后者还可以匹配圆点符号)
Tictoc4.t*c.limit=5
或者
Tictoc4.*.limit=5
又或者
**.limit=5
在 Tkenv图形界面下,你可以通过主窗口左边的对象树来查看模块参数,也可以在模块
属性的”参数(Parameters)”标签上查看。(双击模块图标)
源文件: tictoc4.ned, txc4.cc, omnetpp.ini
4. 使用继承使用继承使用继承使用继承
如果我们认真查看一下 NED 文件就会发现其实 tic 和 toc仅仅是在参数值和显示语句上
不同而已。我们可以通过从一个简单模块类型继承并修改它的一些参数来得到新的简单模块
类型。在本例中,我们得到了两个简单模块类型(tic 和 toc)。只要在网络中定义之后,接下
来我们就可以使用这些类型了。
从现有的简单模块继承是很容易的,这是个基本模块:
simple Txc5
{
parameters:
bool sendMsgOnInit = default(false);
int limit = default(2);
@display("i=block/routing");
gates:
input in;
output out;
}
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 11 –
下面这个是继承的模块,我们只是简单修改了参数值和一些显示属性:
simple Tic5 extends Txc5
{
parameters:
@display("i=,cyan");
sendMsgOnInit = true; // Tic modules should send a message on init
}
Toc看起来很相似,只是参数值不同:
simple Toc5 extends Txc5
{
parameters:
@display("i=,gold");
sendMsgOnInit = false; // Toc modules should NOT send a message on init
}
提示提示提示提示::::C++代码实现可以从基本的简单模块 Txc4那里继承并修改。
一旦我们创建了新的简单模块,我们就可以把它当作网络中的一个子模块类型:
network Tictoc5
{
submodules:
tic: Tic5; // the limit parameter is still unbound here. We will get it from the ini file
toc: Toc5;
connections:
正如你看到的,网络定义变得更短小更简单了。继承能让你在网络中使用普遍共有的类
型,而避免多余的定义和参数设置。
5. 建立建立建立建立延迟处理延迟处理延迟处理延迟处理模型模型模型模型
在之前的模型里,tic 和 toc收到消息后都立即回传,没有处理延迟。接下来我们将给节
点增加 1个仿真秒的处理延迟,然后再回传。在 OMNeT++里,通过模块给自身发送消息来
这种计时。这种消息也被称之为“自消息”。(不同的只是这种消息的使用方式,不然它们也
只是普通的消息对象)
我们在类里增加两个 cMessage指针变量:event和 tictocMsg,分别用来指向我们用来计
时的事件消息和我们要进行仿真其延迟处理的数据包消息。
class Txc6 : public cSimpleModule
{
private:
cMessage *event; // pointer to the event object which we'll use for timing
cMessage *tictocMsg; // variable to remember the message until we send it back
public:
通过 scheduleAt()函数“发送”自消息,指定回传给模块的时机:
scheduleAt(simTime()+1.0, event);
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 12 –
在 handleMessage()函数这里,我们需要区分一个新消息是通过 input输入门到达的,还是自
消息回来的(时钟届满)。这里我们使用:
if (msg==event)
我们也可以这样写:
if (msg->isSelfMessage())
这里可以省略掉计数器,使得代码更简洁。仿真运行结果如下:
源文件: tictoc6.ned, txc6.cc, omnetpp.ini
6. 随机数和参数随机数和参数随机数和参数随机数和参数
本小节我们将介绍随机数。把之前的延迟设为 1s 到一个随机数之间,该随机数可以在
NED文件或者 omnetpp.ini文件里设置。模块参数也可以返回随机数变量,但是,要想使用
这个功能,我们不得不在每次调用 handleMessage()函数的时候去读那个参数。
// The "delayTime" module parameter can be set to values like
// "exponential(5)" (tictoc7.ned, omnetpp.ini), and then here
// we'll get a different delay every time.
simtime_t delay = par("delayTime");
EV << "Message arrived, starting to wait " << delay << " secs...\n";
tictocMsg = msg;
scheduleAt(simTime()+delay, event);
此外,我们将“丢弃”(删除)小概率(hardcoded)的数据包(译者:设置丢包率)
if (uniform(0,1) < 0.1)
{
EV << "\"Losing\" message\n";
delete msg;
}
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 13 –
在 omnetpp.ini里进行参数赋值:
Tictoc7.tic.delayTime = exponential(3s)
Tictoc7.toc.delayTime = truncnormal(3s,1s)
你可以试试,不管你重运行该仿真程序多少次(或者重启它再仿真||重建网络菜单项),得
到的结果是一样的。这是因为OMNeT++使用了一个决策算法(默认是Mersenne Twister RNG)
来产生随机数,该算法用相同的种子来初始化,这一点(可重用)对仿真很重要。你也可以使
用不同的种子,在 omnetpp.ini加入下面语句:
[General]
seed-0-mt=532569 # or any other 32-bit value
从语法上就可以看出,OMNeT++不仅仅只支持 RNG。但是这个教程中的所有模型都是
使用 RNG的。
源文件: tictoc8.ned, txc7.cc, omnetpp.ini
7....超时和时钟中止超时和时钟中止超时和时钟中止超时和时钟中止
为了更进一步地模拟网络协议,我们把之前的模型改成一个“停-等”仿真,这次将把
tic 和 toc的类区分开来,基本场景还是和之前类似:tic 和 toc彼此来回传递消息。但是,在
非零概率下,toc将会发生丢包,这个时候我们需要重传数据包。
下面是 toc的代码:
void Toc8::handleMessage(cMessage *msg)
{
if (uniform(0,1) < 0.1)
{
EV << "\"Losing\" message.\n";
bubble("message lost"); // making animation more informative...
delete msg;
}else
要感谢代码中的 bubble()调用,一旦丢包,toc就会显示一个如图一样的提示(callout):
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 14 –
所以,当 tic 发送消息的时候,它就启动一个计时器,一旦计时器届满,就认定消息
丢失了,然后重新再发一份。如果收到 toc 的回复包,计时器就会被取消。计时器代码
是一条自消息:
scheduleAt(simTime()+timeout, timeoutEvent);
执行 cancelEvent()调用就会取消计时器,这不会妨碍我们反复重用那些超时的消息。
cancelEvent(timeoutEvent);
可以在 txc8.cc文件里阅读到 tic 的全部源代码。
源文件源文件源文件源文件: tictoc8.ned, txc8.cc, omnetpp.ini
8. 消息消息消息消息重传重传重传重传
在本小节理我们对之前的模型进行进一步优化。之前在需要重传时我们就重新创建另一
个数据包,当数据包比较小的时候这个是没有问题的,但在实际生活中,通常采取的措施是
保留一份源拷贝,这样在重传的时候就不需要重新创建数据包。
我们在这里采取的是保留原始数据包,而把拷贝副本发送出去,当 toc的应答包到来的
时候,我们才删除原始包。为了便于直观地检验该模型,我们在每个消息上增加了一个消息
序列号。
为了避免 handleMessage()过于庞大,我们把相关的代码分成两个新函数:
generateNewMessage()和 sendCopyOf(),并通过 handleMessage()函数调用它们。
函数如下:
cMessage *Tic9::generateNewMessage()
{
// Generate a message with a different name every time.
char msgname[20];
sprintf(msgname, "tic-%d", ++seq);
cMessage *msg = new cMessage(msgname);
return msg;
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 15 –
}
void Tic9::sendCopyOf(cMessage *msg)
{
// Duplicate message and send the copy.
cMessage *copy = (cMessage *) msg->dup();
send(copy, "out");
}
源文件: tictoc9.ned, txc9.cc, omnetpp.ini
三三三三、、、、 模拟真实网络模拟真实网络模拟真实网络模拟真实网络
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 16 –
9. 两个以上节点组成的网络两个以上节点组成的网络两个以上节点组成的网络两个以上节点组成的网络
现在我们来进行一次比较大的跨越:创建几个 tic 模块并把它们连成一个网络。它们所
做的事情很简单:其中一个节点产生一个消息,其他节点随机传送该消息,直到它到达预先
指定的目标节点。
相应的 NED 文件需要做一些修改,首先,Txc模块必须包含多个 input和 output门:
simple Txc10
{
parameters:
@display("i=block/routing");
gates:
input in[]; // declare in[] and out[] to be vector gates
output out[];
}
方括号[]把之前的门变成了门向量,当我们用 Txc 来建立网络的时候,才决定其向量的
大小(门数)。
network Tictoc10
{
submodules:
tic[6]: Txc10;
connections:
tic[0].out++ --> { delay = 100ms; } --> tic[1].in++;
tic[0].in++ <-- { delay = 100ms; } <-- tic[1].out++;
tic[1].out++ --> { delay = 100ms; } --> tic[2].in++;
tic[1].in++ <-- { delay = 100ms; } <-- tic[2].out++;
tic[1].out++ --> { delay = 100ms; } --> tic[4].in++;
tic[1].in++ <-- { delay = 100ms; } <-- tic[4].out++;
tic[3].out++ --> { delay = 100ms; } --> tic[4].in++;
tic[3].in++ <-- { delay = 100ms; } <-- tic[4].out++;
tic[4].out++ --> { delay = 100ms; } --> tic[5].in++;
tic[4].in++ <-- { delay = 100ms; } <-- tic[5].out++;
}
在这里我们创建了一个模块向量,包含 6个模块,模块连接后的拓扑如下:
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 17 –
在这个版本里,tic[0]将产生消息并发送给周围节点,这个是在调用初始化函数 initialize()
完成的,初始化函数调用 getIndex()函数,该函数返回该模块在向量中的索引。
当一个消息到达节点的时候,handleMessage()函数就会调用 fowwaredMessage(),获取一
个随机的的门编号(编号在门向量范围内),然后从该门发送出消息。
void Txc10::forwardMessage(cMessage *msg)
{
// In this example, we just pick a random gate to send it on.
// We draw a random number between 0 and the size of gate `out[]'.
int n = gateSize("out");
int k = intuniform(0,n-1);
EV << "Forwarding message " << msg << " on port out[" << k << "]\n";
send(msg, "out", k);
}
当消息到达 tic[3]时,它的 handleMessage()函数将会删除这个消息。
全部源代码见 txc10.cc
练习:你可以注意到这里的路由效率并不高,有时候数据包可能会在两个节点间来回传
递数次后,再被转发到另外节点。在这里可以进一步改进,使得数据包不会被回送到发送者。
提示:cMessage::getArrivalGate(), cGate::getIndex(). 如果消息不是通过门而是通过自消息
到达的,那么 getArrivalGate()返回 NULL。
源文件源文件源文件源文件::::tictoc10.ned, txc10.cc, omnetpp.ini
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 18 –
10. 信道和内部类型定义信道和内部类型定义信道和内部类型定义信道和内部类型定义
我们刚才定义的网络显得太过复杂和冗长,特别是连接部分。让我们试着来简化一下。
首先我们注意到基本上所有的连接都使用相同的延迟参数,可以给这些相似的连接(信道)创
建类型。我们可以创建一个信道类型来指定延迟参数,然后在网络中的所有连接上使用这种
类型。
network Tictoc11
{
types:
channel Channel extends ned.DelayChannel {
delay = 100ms;
}
submodules:
正如你所看到的,我们通过在网络定义(network)里添加一个 types段来定义一个新的信
道类型。这种类型定义仅在网络内部可见,所以它被称为本地(local)或者内部(inner)类型,
你可以把简单模块作为内部类型使用,随你所欲。
提示:我们通过内建一个 DelayChannel来创建信道。(内建信道可以在 ned包里找到,
所以我们在 extents关键字后面使用了类型全名 ned.DelayChannel)
现在让我们查看一下连接 connections部分的变化。
connections:
tic[0].out++ --> Channel --> tic[1].in++;
tic[0].in++ <-- Channel <-- tic[1].out++;
tic[1].out++ --> Channel --> tic[2].in++;
tic[1].in++ <-- Channel <-- tic[2].out++;
tic[1].out++ --> Channel --> tic[4].in++;
tic[1].in++ <-- Channel <-- tic[4].out++;
tic[3].out++ --> Channel --> tic[4].in++;
tic[3].in++ <-- Channel <-- tic[4].out++;
tic[4].out++ --> Channel --> tic[5].in++;
tic[4].in++ <-- Channel <-- tic[5].out++;
}
正如你所看到的一样,我们现在只需要在连接定义时指定信道名字,这样当整个网络的
延迟参数发生变化的时候,便于修改。
源文件源文件源文件源文件::::tictoc11.ned, txc11.cc, omnetpp.ini
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 19 –
11. 使用使用使用使用双向连接双向连接双向连接双向连接
如果我们更仔细地检查刚才的连接部分,我们可以发现每个节点对通过两个连接连在一
起,每个方向各一个连接,OMNeT++提供了双向连接,我们可以使用它。
首先,我们需要定义一个双向门(inout)来取代之前的 input和 output门。
simple Txc12
{
parameters:
@display("i=block/routing");
gates:
inout gate[]; // declare two way connections
}
新的连接部分看起来应该是下面这样的:
connections:
tic[0].gate++ <--> Channel <--> tic[1].gate++;
tic[1].gate++ <--> Channel <--> tic[2].gate++;
tic[1].gate++ <--> Channel <--> tic[4].gate++;
tic[3].gate++ <--> Channel <--> tic[4].gate++;
tic[4].gate++ <--> Channel <--> tic[5].gate++;
}
之前我们修改了门的名称,所以我们需要修改 C++代码。
void Txc12::forwardMessage(cMessage *msg)
{
// In this example, we just pick a random gate to send it on.
// We draw a random number between 0 and the size of gate `gate[]'.
int n = gateSize("gate");
int k = intuniform(0,n-1);
EV << "Forwarding message " << msg << " on gate[" << k << "]\n";
// $o and $i suffix is used to identify the input/output part of a two way gate
send(msg, "gate$o", k);
}
提示提示提示提示:在门名称 gate后面的$i 和$o后缀分别代表连接的两个方向(输入和输出)。
源文件源文件源文件源文件:tictoc12.ned, txc12.cc, omnetpp.ini
12. 自自自自定义我们的消息类定义我们的消息类定义我们的消息类定义我们的消息类
本小节里,目标地址不再是指定的 tic[3],我们将使用一个随机的目标地址并让消息带
上该地址。
最好的方法是派生 cMessage子类并添加一个数据成员。手动编码消息类通常会很冗长,
因为包含很多的样板代码(boilerplate code),所以我们让 OMNeT++为我们创建这个类。见
tictoc13.msg文件:
message TicTocMsg13
{
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 20 –
int source;
int destination;
int hopCount = 0;
}
建立 makefile以便调用消息编译器 opp_msgc来产生 tictoc13_m.h和 tictoc13_m.cc文件。
它们将包含从 cMessage派生而来的 TicTocMsg13子类。该子类在每个域上都有 getter和
setter//
接着在我们的 C++代码中添加如下语句,包含 tictoc13_m.h文件,就可以像其他类一样使
用 TicTocMsg13类了。
#include "tictoc13_m.h"
举个例子,在 generateMessage()函数中使用以下语句来创建消息并给各个数据域赋值:
TicTocMsg13 *msg = new TicTocMsg13(msgname);
msg->setSource(src);
msg->setDestination(dest);
return msg;
然后,消息处理函数的开头部分如下:
void Txc13::handleMessage(cMessage *msg)
{
TicTocMsg13 *ttmsg = check_and_cast<TicTocMsg13 *>(msg);
if (ttmsg->getDestination()==getIndex())
消息处理函数带有一个 cMessage的指针参数,但是如果我们把消息类型强制转换为
TicTocMsg13 *后,就只能访问在 TicTocMsg13里所定义的域了。使用 C 风格的类型转换并
不安全,因为消息有可能根本就不是 TicTocMsg13类型,可能会导致程序崩溃,而且出现
一个很难调试的报错。
C++提供了一个叫做 dynamic_cast的解决办法,在这里我们使用 OMNeT++提供的
check_and_cast<>();它尝试用 dynamic_cast来转换指针,如果失败的话,则停止仿真并出
现类似如下提示:
下面一行,检查目标地址和节点地址是不是同一个。getIndex()成员函数返回子模块向量
里的模块指针(记住:我们在 NED 文件里声明是 Txc13[6],所以节点地址从 0到 5)。
为了延长仿真时间,在一个消息到达目的地址后,目标节点将会产生新的消息并路由转
发出去。请参阅源代码 txc13.cc。动画效果如下:
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 21 –
可以双击消息来打开一个观察窗口(如果你的手部动作不够快,可以暂停仿真再点击)。
观察窗口会显示很多有用的信息;消息的各个域的内容可以在内容(Contents)标签页上查看。
源文件源文件源文件源文件:tictoc13.ned, tictoc13.msg, txc13.cc, omnetpp.ini
练习:在本模型中任何一个时刻都只有一个消息在转发,节点只在收到消息后才产生新
消息,这样做是为了让仿真更容易一些。可以修改模块让它周期性产生消息,消息间隔时间
作为一个模块参数,服从指数分布。
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 22 –
四四四四、、、、 增加统计收集增加统计收集增加统计收集增加统计收集
13. 显示消息收发数目显示消息收发数目显示消息收发数目显示消息收发数目
通过给模块类加入两个计数器:numSent和 numReceived,就可以在运行时记录下每个
节点收发的消息数。
class Txc14 : public cSimpleModule
{
private:
long numSent;
long numReceived;
protected:
初始化时设置为 0,并设为 WATCH 函数的参数,现在,就可以通过使用 Find/inspect
对话框(Inspect菜单,或者在工具栏上也有)来查看各个节点的消息收发数目。
在离散仿真模型中,各节点的收发数目大致相同,这说明均匀分布函数 intuniform()工作
正常。但是在现实仿真中你可以快速了解模型中各节点的状态,这点很有用。
也可以让这些信息显示在模块图标的上面,我们只需要在运行期间修改显示字符串。代
码如下:
if (ev.isGUI())
updateDisplay();
void Txc14::updateDisplay()
{
char buf[40];
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 23 –
sprintf(buf, "rcvd: %ld sent: %ld", numReceived, numSent);
getDisplayString().setTagArg("t",0,buf);
}
运行结果:
源文件源文件源文件源文件:tictoc14.ned, tictoc14.msg, txc14.cc, omnetpp.ini
14. 增加统计收集增加统计收集增加统计收集增加统计收集
OMNeT++仿真内核可以自动记录一个有关消息交换历史的详细日志,通过在
omnetpp.ini文件中设置如下配置选项:
record-eventlog = true
之后,日志文件就可以在 IDE 里显示了(见下一节:Sequence charts end event logs)
提示提示提示提示::::记录下的日志文件可能比较庞大,所以建议仅在需要时使用此功能。
之前的仿真模型做了这么多有趣的事,使得我们可以收集一些统计信息。例如,你可以
对一个消息在到达终点前所经历的平均跳数感兴趣。
我们将把每条消息到达前的跳数记录在一个输出向量里(该向量是个按照时间顺序的
(tim,value)对的序列),同时我们也会计算每个节点的均值、标准差、最小值和最大值,并在
仿真结束时写入保存到一个文件里,然后再使用 OMNeT++ IDE提供的工具进行分析。
要想这样,我们在类里增加一个输出向量对象(把数据记录到 Tictoc15-0.vec里)和一个柱
状图对象(计算均值等):
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 24 –
class Txc15 : public cSimpleModule
{
private:
long numSent;
long numReceived;
cLongHistogram hopCountStats;
cOutVector hopCountVector;
protected:
当消息到达目标节点时,我们即更新统计信息。需要将下述代码添加到 handleMessage():
hopCountVector.record(hopcount);
hopCountStats.collect(hopcount);
调用 hopCountVector.record()函数把数据写进 Tictoc15-0.vec,当仿真模型很大或者执行
时间较长时,Tictoc15-0.vec 文件将会变得很巨大。你可以通过在 omnetpp.ini 文件里指定
要你想要的向量来解决这个问题,或者你也可以指定你感兴趣的仿真时间段(时间段以外的
数据将被丢弃)。
当开始一个新仿真的时候,之前的 Tictoc15-0.vec 文件将被删除。
由仿真中柱状图对象所搜集到的标量数据(Scalar data)需要手动记录(在 finish 函数中),
当仿真成功完成后,自动调用 finish 函数,下述代码中的 recordScalar()函数把结果写入
Tictoc15-0.sca文件中:
void Txc15::finish()
{
// This function is called by OMNeT++ at the end of the simulation.
EV << "Sent: " << numSent << endl;
EV << "Received: " << numReceived << endl;
EV << "Hop count, min: " << hopCountStats.getMin() << endl;
EV << "Hop count, max: " << hopCountStats.getMax() << endl;
EV << "Hop count, mean: " << hopCountStats.getMean() << endl;
EV << "Hop count, stddev: " << hopCountStats.getStddev() << endl;
recordScalar("#sent", numSent);
recordScalar("#received", numReceived);
hopCountStats.recordAs("hop count");
}
这些文件(向量和标量统计信息)保存在 results子目录下。
你也可以在仿真过程中观察数据:在模块属性里可以直接查看 hopCountStats和
hopCountVector对象,双击后可以看到他们被初始化为空,然后运行仿真(Fast 模式或者
Express模式)来获取足够的数据用来显示,过一会你就会看到类似如下画面:
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 25 –
当你认为已经收集到足够的数据后,停止仿真,接下来可以离线对这些结果文件
(Tictoc15-0.vec 和 Tictoc15-0.sca)进行分析。可以通过 Simulate 菜单—Call finish()或者点击工具栏上的相应按钮来停止仿真,该操作会调用 finish 函数并把数据写入
Tictoc15-0.sca。
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 26 –
五五五五、、、、 通过通过通过通过 Plove 和和和和 Scalars 可视化结果可视化结果可视化结果可视化结果
15. 标量标量标量标量、、、、向量输出可视化向量输出可视化向量输出可视化向量输出可视化
OMNeT++ IDE可以帮你分析结果数据。它可以过滤、处理和显示向量及标量数据,当
然也可以显示柱状图。下面的图表就是 IDE 的结果分析工具所创建的。
提示提示提示提示::::如欲进一步了解图表显示和处理功能,参见 OMNeT++用户指南。
上一次的模型中记录了一个消息每次到达目标节点所经历的跳数,下图显示了节点 0和
节点 1的这些向量结果:
如果我们应用一个求均值的操作,就可看到不同节点的跳数如何趋向一个平均值:
下图显示了到达每个目标节点的消息所经历平均跳数和最大跳数,基于仿真结束后
所记录的标量数据:
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 27 –
下图显示了跳数分布的柱状图:
16. Sequence charts end event logs
OMNeT 仿真内核可以把仿真期间的消息交换历史记录到一个日志文件。该文件稍后可
以用顺序图工具(Sequence Chart tool)进行分析。
下图是通过顺序图工具创建的,显示了消息在网络中两个不同节点之间是如何被路由转
发的。这个例子中的图标非常简单,但是当你的模型很复杂的时候,顺序图在进行调试
(debugging)、探测(exploring)和记录(documenting)模型行为的时候非常有价值。
OMNeT++ 4.07 入门教程
湖南大学计算机与通信学院 嵌入式系统与网络实验室
- 28 –
17. 总结总结总结总结
希望本教程能对你学习 OMNeT++有用,欢迎提出评论和建议。
本文由本文由本文由本文由 sunfast 翻译翻译翻译翻译,,,,任何转载和引用都是受欢迎的任何转载和引用都是受欢迎的任何转载和引用都是受欢迎的任何转载和引用都是受欢迎的
Email::::[email protected]
QQ:::: 249999240
Blog: http://sunfast.cublog.cn
2008200820082008----11111111----00007777 04:35 04:35 04:35 04:35 am am am am