首先解决一些概念上的问题:
1. 在omnetpp.org中提到的仿真模型和框架与OMNet++是什么关系?
OMNet++提供了基本的工具和机制来编写仿真代码,但它本身并不提供任何特定用于计算机网络仿真,系统架构仿真和任意其它领域的组件;具体的仿真是由一些仿真模型和框架如Mobility Framework或INET Framework来支持,这些模型独立于OMNet++开发,并有自己的发布周期。
2. OMNet++提供了什么?
一个C++库,它由仿真内核及一些用来创建仿真组件(简单模块和信息)的工具类(如随机数生成,统计收集,拓扑发现等);组装和配置这些组件的基础设施(NED语言,ini文件);运行时用户接口或仿真环境(TKenv,Cmdenv);一个用来设计,运行和评估仿真的IDE环境;实时仿真的扩展接口;MRIP,并行的分布式仿真,数据库连接等等这些组成。
3. OMNet++的仿真模型是什么样的?
OMNet++提供了一个基于组件的架构,模型是由可重用的组件或模块组成的。模块之间可以通过gates(在其它系统中称为ports,即端口)进行连接,以构成复合模块。每个仿真模型是一个复合模块类型的实例。这一层次(组件和拓扑)由NED文件来处理。
NED文件只定义了模型的结构(拓扑),其行为和模块参数的某个子集则是开放的,如前面所提到的,行为是通过在简单模块相关联的C++代码来定义的,而在NED文件中没有赋值的模块参数则从ini文件中获取它们的值。
5. 仿真的输出是什么?
仿真过程中会有向量输出和标题输出,默认文件名为omnetpp.vec和omnetpp.sca,尽管omnetpp.ini可以指定输出为不同的文件名,仍需要在简单模块中进行编码以让其拥有记录仿真结果的能力,所以在他人编写的仿真中可能并不创建这些文件。
一个输出向量文件包含若干输出向量,每一个向量都是一个 (timestamp,value) 对。输出向量可以存储例如关于时间的队列长度,接收数据包时端到端的延迟,丢弃的数据包或者信道吞吐量—任意在简单模块中的编程所定义的统计量都可以。并且,可以在omnetpp.ini中配置输出向量:即可以启用或者关闭记录某个输出向量,或者用一定的仿真时间间隔来进行限制。可以通过查看cOutVector对象的C++的源代码来查看一个简单模块可以获取的输出向量。
输出向量根据时间来捕获行为,而输出标量文件则包含总的统计量:发送包的数目,丢弃包的数目,平均的端到端延迟,吞吐量的峰值等。可以在recodScala() 方法的调用 ,特别是在一个简单模块类中的finish()方法中查看输出标量。
输出向量可以用Plove程序来绘图,而输出标量则可以使用Scalars程序来绘图。
6. 关于随机数
OMNeT++默认的随机数生成器是 Mersenne Twister,种子可以自动获取,或者在omnetpp.ini中定义。OMNeT++ 支持很多种概率分布,并且在NED和C++中都是可用的(在 3.2 版本中有 14 个连续的分布和 6 个离散的分布, 具体可以查看 API doc)。非常量的模块参数可以用随机变量来赋值,如 exponential(0.2), 它表示C++代码会在每次读取参数时获得一个不同的数字;这是指定随机流量源的一种很方便的方法。( 常量参数也能用表达式如exponential(0.2)来赋值,但它只会被计算一次并且不再改变)。
7. 是否可以在OMNeT++中进行MRIP,分布式并行仿真,网络模拟,或feature X?
可以。OMNet++ 具有很强的可扩展性并且开放源代码,一些特性是可以开箱即用的:
MRIP是指 multiple replications in parallel,而Akaroa是一个很出色的工具。你可以单独地下载并安装它,然后启用OMNet++的Akaroa支持进行重新编译,但AFAIK Akaroa仅可以在 Linux(*nix)中使用,在OMNeT++的手册中可以找到更多信息。
如果仿真需要大量的内存,则可以在集群中运行仿真。分块和其它配置可以在omnetpp.ini中定义,但仿真模型本身却不用进行改变(除非模型包含全局变量可能导致不能分布地运行)。通信层使用的是MPI,但事实上这是可配置的,所以如果你没有MPI的话也仍然可以通过命名管道,或者基于文件的信息交换来进行一些基本的测试。当然如果需要,你也可以通过实现abstract cParsimCommunications接口来添加一个新的方案。
网络模拟,实时仿真和类似于hardware-in-the-loop的功能是可用的,这是因为在仿真内核中的事件调度器是可插拔的。OMNet++的一个Demo展示了如何进行实时仿真和一个简单的网络模拟的样例,足以让你进入这个领域。Real network emulation with the INET Framework is in the queue.
可以用一个数据库来代替omnetpp.ini文件作为配置数据的源,也能将仿真结果重定向到数据库中,OMNeT++ 中包含这样一个Demo,可以作为参考。
由于你拥有全部的源代码和良好的文档,所以你能根据自己的想法来实现很多事情。
8. 如何在C++代码中编写模型?
简单模块其实是C++类,你从cSimpleModule继承子类,重定义一个虚成员函数,并通过Define_Module()宏来将新的类注册到OMNeT++中。
模块间主要使用消息传递进行通讯,而timers(timeouts)也处理模块发送给自身的消息。消息应当是cMessage类或者是其子类,消息将被传递到模块的 handleMessage(cMessage *msg) 方法,在这里应当添加你的代码。几乎你想定义的模块行为都在handleMessage() 之中定义,因此这段代码块可能会很长,所以将其重构为其它成员函数例如命名为processTimer(),processPacket()是一个好主意(可以将activity() 方法视为handleMessage()的一个替换,但是在实践中最好不要这么做。)。
你可以使用send(cMessage *msg, const char *outGateName) 方法来向其它模块传递消息,对于无线仿真和一些情况下,则可以更方便地直接传递消息到其它模块而不用在NED文件中建立连接。这可以由sendDirect(cMessage *msg, double delay, cModule *targetModule, const char *inGateName)方法来完成。Self-messages (that is, timers can be scheduled) 可以通过scheduleAt(simtime_t time, cMessage *msg) 进行传送,并且在它们失效前可以通过cancelEvent(cMessage *msg) 来取消。这些函数都是cSimpleModule类的成员,在文档中可以找到更多的相关信息。
基本的cMessage类包含几个数据成员,最重要也最实用的是name,length,message”kind”(一个int成员)。其它数据成员则存储此消息最近发送/调度的信息:arrival time,arrival gate等。为了进行协议仿真,cMessage可以封装另外一个cMessage对象,可以查看它的encapsulate(cMessage *msg) 方法进行了解。这些方法也能逐步地修改域的长度。如果你需要在消息中运送更多的数据,其它数据成员可以通过继承来添加。然而,你并不需要手工编写新的C++类,可以更方便地在一个.msg文件中定义,从而让OMNet++( opp_msgc 工具) 来为你生成C++类。生成的C++文件会拥有_m.h,m.cc的后缀。.msg文件支持进一步的继承,组合,数组成员等等,并且它拥有可以让在C++类中自定义的语法。一个消息文件的例子如下:
1 |
message NetworkPacket { |
2 |
fields: |
3 |
int srcAddr; |
4 |
int destAddr; |
5 |
} |
OMNet++经常用来进行网络协议的仿真,cMessage有一个叫control info的域,它包含额外的信息来促进协议层间的通讯。例如,当应用层发送数据(一个消息对象)到TCP来传输时,它能在包含socket标志符的信息中附加一小段控制信息(一个对象)。或者,当TCP发送一个TCP segment到IP层传输时,它附加包含目标IP地址的控制信息,也可能是其它的一些选项,如TTL。控制信息也能发送到其它方向(向上),以便向上一层标记源IP地址或者TCP连接。控制信息是通过cMessage的 setControlInfo(cPolymorpic *ctrl) 和 removeControlInfo() 方法来处理的。
其它cSimpleModule模块中需要重定义的虚成员函数是initialize()( 主要的初始化工作在这里进行,因为在构造函数的调用过程中模型也正在构建),initialize(int stage) 和多阶段初始化的int numinitStages() const(这在一个模块的初始化代码依赖于其它已经初始化完毕的模块时很有用),finish()来记录总的结果(析构函数不适宜用于这种用途)。如果你想要模块感知参数(参数可以交互式地在GUI中或由其它模块改变,因此你需要重新读取这个参数)在运行时的改变,则需要重定义handleParameterChange(const char *paramName)方法。
简单模块的NED参数可以使用par(const char *paramName) 方法来读取,在典型的情况下,这个过程会在initialize()中进行,并将这些值存储在模块类的数据成员中。par()返回一个cPar对象的引用,这个引用可以用C++的语法或调用对象的doubleValue()方法等这些途径转为合适的类型(long,double,const char*,etc)。除了基本的类型,也可以是XML文件:参数可以赋值到XML文件中,它能用以DOM的对象树的形式向C++代码展示。
消息传递并不总是模块间通讯的最好方法。例如,如果你设计了一个用于收集统计的模块(常使用全局变量),通过消息来传递统计量的更新,不如将统计模块作为一个C++对象,并调用它的用于此目的的公共成员函数(如updateStatistics(…))来得简便。
对方法的直接调用有一些技术细节。首先你需要查找其它模块:cModule的parentModule() 和submodule(const char*name)方法可以查找到与当前相关的模块,而simulation.moduleByPath(const char *path) 可以通过一个绝对的路径名在全局查找一个模块,一旦你拥有其它模块的指针,你需要将它转化为实际的类型(i.e. to StatisticsCollector* from cModule*),这是由拥有与C++的dynamic_cast相同语法的check_and_cast来完成,但如果转换不成功或指针为NULL时它会抛出一个错误。public的方法应当将Enther_Method()或者Enter_Method_Silent(…) 放置在顶端,它们启用了GUI的动画调用,同时进行了一些与临时上下文切换相近的行为(在这里并不深究,但如果想要在方法中进行消息处理这是必须的)。INET框架广泛地使用了方法调用来访问模块,如RoutingTable, InterfaceTable, NotificationBoard等。
INET的NotificationBoard在一些仿真中很有用,它通过对信息的产生者和消费者进行解耦,在它们之间转发变化的notifications或者事件的notifications,从而支持几个模块间的信息共享(NotificationBoard宽泛地基于blackboard 区域,但在实践中,仅转发notifications比存储blackboard中发布的信息更简单有效。进一步地,notifications可能包含实际的数据或者指向数据的指针的拷贝)。
对于调试,模块中打印一些必要的信息是很关键的。在OMNet++中使用ev<< (类似于printf和cout)将输出写到可以进行过滤的GUI窗口中,同时模块用图标的颜色或文件标签及tooltips来展示状态也有助于减少调试的时间;这个可以通过在执行时改变display string来完成(查看cModule's displayString())。通过调用bubble(const char*text)方法,可以在模块的上方看到一个短暂出现的“气泡”或“气球”,这也是很有帮助的。另外一种调试的辅助是WATCH(variableName) 宏及其变种 (WATCH_VECTOR, 等),它能让你在Tkenv GUI中查看变量的值(you'll find watched variables in the "Contents" tab of the module's inspector)。
为了记录输出向量,你应当添加一个cOutVector对象到类中作为一个数据成员(或者用new来创建),设置name string作为输出向量的名字,然后持续调用它的record(double value)方法来记录数字。输出标量最好编写在模块(请查看cModule中的 recordScalar(const char *name, double value) 方法)的finish() 函数中,可以基于计数器作为模块的数据成员。也可以计算基本的统计和图表:请参考cStdDev, cDoubleHistogram and cLongHistogram classes 这些类。
NED文件描述静态的拓扑,但也能动态地创建模块和连接。这在网络拓扑不是以NED文件(如普通文本文件,Excel sheet,database等)存储,或者你想在运行时动态地创建和删除模块的情况下很有用。在前一种情况下,一般可以通过Awk,Perl,Python,Ruby,TCL或在文本编辑中一系列的查找/匹配操作来将数据转化为NED。然而,当你决定以C++来创建动态模块时,对一个样本NED文件调用nedtool并查看生成的_n.cc文件会很有帮助。
接着动手编写一个工程来运行:
可以参照这篇文章http://leeing.org/2010/01/24/the-ned-language-of-omnetpp-4-0/