这里写自定义目录标题
- Lines beginning with `#' are comments
第一章 无线传感器网络概述 概述 集成了传感器、微机电系统和网络三大技术而形成的传感器网络是一种全新的信息获取和处理技术,具有广阔的应用前景。主要表现在军事、环境、医疗、家庭等商业领域,特别在空间探索和灾难拯救等特殊的领域有其得天独厚的技术优势。为评价传感器网络协议算法的性能,仅通过实验是无法实现的,特别是包含大量节点的大规模无线传感器网络,更是很难通过实验来实现(实际上,上百个节点的实验己经比较难以管理与实现)。 为了实现无线传感器网络的仿真,研究人员设计开发了许多的仿真平台(或在现有平台建立无线传感器网络模型),包括NS-2, OPNET, SensorSim, EmStar, OMNet++, G1oMoSim, TOSSIM, PowerTOSSIM等。 1.1 NS-2 NS-2 (Network Simulator-2)是著名的用于网络研究的离散事件仿真工具,里面包括了大量的用于在有线或无线、本地连接或通过卫星连接进行TCP协议、路由算法、多播协议仿真的网络协议、调度器和工具。NS-2主要致力于OSI模型的仿真,包括物理层的行为。NS-2可以对仿真进行详细的跟踪并用仿真工具“网络动画播放器”C Network Animator (NAM)进行回放。NS-2是开放源码的自由软件,可以免费下载。有一些研究小组对NS-2进行了扩展,使它能支持无线传感器网络的仿真,包括传感器模型、电池模型、小型的协议栈、混合仿真的支持和场景工具等。由于NS-2对数据包级进行非常详细的仿真,接近于运行时的数据包数量,使得其无法进行大规模网络的仿真。 1.2 OPNET OPNET建模工具是商业化的通信网络仿真平台。OPNET采用网络、节点和过程三层模型实现对网络行为的仿真。其无线模型是采用基于流水线的体系结构来确定节点间的连接和传播,用户可指定频率、带宽、功率和包括天线增益模式和地形模型在内的其它特征。OPNET提供了很多的模型,包括TCP/IP, 802.1 1 , 3G等。并已有一些研究人员在OPNET上实现对TinyOS的NesC程序的仿真。 但要实现无线传感器网络的仿真,还需要添加能量模型,而OPNET本身似乎更注重于网络QoS的性能评价。 1.3 SensorSim SensorSim是建立在NS-2的一个采用DSR的802.11网络模型上的。SensorSim是用于WINS平台的,需要用SensorWare Tel脚本进行设计。SensorSim在仿真时跟踪了节点的能量使用情况,其能量模型来自WINS节点,使得其无法用于Mote平台的仿真。另外,SensorSim已经停止开发和支持,也无法下载到程序代码。 1.4 EmStar EmStar提供了在仿真和基于iPAQ的运行Liunx的节点之间灵活切换的环境,用户可以选择在一个主机上运行多个虚拟节点进行仿真,也可以在一个主机上运行多个与真实的节点进行桥接的虚拟节点。EmSta:可以将无线传感器网络部署在一个友好的基于Linux的环境中,并进行跟踪和调试程序。EmTOS是用于在EmStar中进行TinyOS程序仿真的工具。 EmStar虽然不是一个真正意义上的无线传感器网络仿真工具,但却是一个很有用的用于对传感器网络的应用程序进行测试的环境。 1.5 GloMoSim GloMoSim (Global Mobile Information Systems Simulation Library )是一个可扩展的用于无线和有线网络的仿真系统,根据OSI进行分层的建模设计。应用层、传输层、网络层、数据链路层、数据包接收模型、无线电模型和无线电波传播模型等不同的层次之间采用标准的APIs进行仿真。GIoMoSim采用Parsec进行设计开发,提供了对并行离散时间仿真的支持。G1oMoSim目前仅支持纯无线网络的协议仿真,可用于仿真传感器网络中的物理信道特征和数据链路协议的时延等特性。 1.6 TOSSIM TOSSIM (TinyOS mote simulator)是用于对采用TinyOS的Motes进行bit级的仿真的工具。TOSSIM将TinyOS环境下的NesC代码直接编译为可在PC环境下运行的可执行文件,提供了不用将程序下载的真实的Mote节点上就可以对程序进行测试的一个平台。TOSSIM还提供了用于显示仿真情况的用户界面TinyViz.。TOSSIM的缺点是没有提供能量模型,无法对能耗有效性进行评价。 1.7 PowerTOSSIM PowerTOSSIM是对TOSSIM的扩展,采用实测的MICA2节点的能耗模型对节点的各种操作所消耗的能量进行跟踪,从而实现无线传感器网络的能耗性能评价。PowerTOSSIM的缺点是,所有节点的程序代码必须是相同的,而且无法实现网络级的抽象算法的仿真。
第二章 Omnet++简介 概述 目前,存在的网络通用协议十分有限。Ethernet普遍用于有线局域网,IEEE 802.11主要用于无线局域网,而TCP则为不可靠的媒质提供可靠的传输。然而,对于传感器网络来说,它没有这种权威的协议或算法,将来也未必能有,因为传感器网络通常是基于特定应用的。另外,无线传感器网络的设计需要同时考虑能量效率、容错率、同步、服务质量、调度方法、系统拓扑等的影响。因此,ns-2等网络模拟器对于无线传感器网络的仿真是有局限的。本书将介绍另一种新的网络模拟器OMNeT++ ( Objective Modular Network Testbed in C++),并运用它进行无线传感器网络协议算法的仿真。 OMNeT++是Objective Modular Network TestBed in C++的英文缩写,它是开源的基于组件的模块化的开放网络仿真平台,是近年来在科学和工业领域里逐渐流行的一种优秀的网络仿真平台。OMNeT++作为离散事件仿真器,具备强大完善的图形界面接口和可嵌入式仿真内核,同NS2,OPNET和JavaSim等仿真平台相比,OMNeT++可运行于多个操作系统平台,可以简便定义网络拓扑结构,具备编程,调试和跟踪支持等功能。OMNeT++主要用于通信网络和分布式系统的仿真,目前最高版本为OMNeT3.3p1。 2.1 OMNeT++框架 2.1.1 OMNeT++组成 OMNeT++主要由六个部分组成:仿真内核库(simulation kernel library,简称Sim),网络描述语言的编译器(network description compiler, nedc),图形化的网络编辑器(graphical network description editor,GNED),仿真程序的图形化用户接口-Tkenv,仿真程序的命令行用户接口-Cmdenv,图形化的输出工具-Plove和Scalar。 Sim是仿真内核和类库,用户编写的仿真程序要同Sim连接,Sim在OMNeT++中占据最为核心重要的地位。下面详细介绍的另外两重要组成部分。 (1).网络描述(NED)语言 NED是模块化的网络描述语言。网络描述包括大量的对组件的描述,如通道,简单和复合模块的类型。这些组件描述可用于各种不同的网络描述中。NED语言用来定义模型中的网络拓扑结构,较为简单的网络拓扑可以使用GNED,但复杂网络的拓扑描述还应该用NED源文件方式书写。 (2).用户接口 OMNeT++的用户接口用于实现仿真程序的人机交互,OMNeT++允许模型内部机制对用户可视化,也允许用户启动和终止仿真,并更改模型内部的变量。OMNeT++中的图形化接口是一个用户工具,可方便用户了解模型内部的运行机制。 用户接口和仿真内核的交互是通过一个已定义的接口实现的。无需改变仿真内核,就可以实现不同类型的用户接口。同样无需更改模型文件,仿真模型可在不同接口下运行。用户以在强大图形化用户接口下测试和调试仿真程序,并最后可在简单快速的用户接口中运行,而且该接口支持批处理。 目前OMNeT++支持两种用户接口,即Tkenv和Cmdenv。对仿真进行的测试和调试可以在Tkenv接口下进行,Tkenv是一个简便易用的图形窗口化的用户接口,Tkenv支持跟踪,调试和执行仿真的功能。它在执行仿真过程中的任意时刻都能够提供详细的状态信息。Tkenv的主要特征有:各模块的文本输出有其独立的窗口,仿真过程中可以在Tkenv窗口中看到自传消息,支持仿真动画,标记断点,具有检查窗口,可以检查和改变模型中的变量,执行过程中仿真结果的图形化显示并且结果可以用柱状图和时间序列图显示,仿真可重新进行,快照文件用于显示模型的详细信息。 Cmdenv接口用于实际的仿真实验,因为Cmdenv支持批处理。Cmdenv是一个简便的小型命令行接口,执行速度快。它可以在所有操作系统平台上运行。Cmdenv可以一次批处理配置文件中所有的仿真。 2.1.2 OMNeT++结构 (1) OMNeT++具有模块化的结构,图1是OMNeT++仿真的高层体系结构。
图1的箭头表示两组件之间的交互,图中共有5个箭头,表示了组件间的5种关系。 (1)执行模型和Sim:仿真内核管理将来的事件,当有事件发生时,仿真内核就调用执行模型中的模块。执行模型的模块存储在Sim的main对象中。执行模型依次调用仿真内核的函数并使用Sim库中的类。 (2)Sim和模型组件库:当仿真开始运行创建了仿真模型的时候,仿真内核就实例化简单模块和其它的组件。当创建动态模块时,仿真内核也要引用组件库。实现在模型组件库中注册和查寻组件也是Sim的功能。 (3)执行模型和Envir:ev对象作为Envir的一部分,是面向执行模型的用户接口。仿真模型使用ev对象来记录调试信息。 (4)Sim和Envir:由Envir决定创建何种模型,Envir包含主要的仿真循环,并调用仿真内核以实现必须的功能。Envir捕捉并处理执行过程中发生在仿真内核和或类库中的错误和异常。 (5)Envir和Tkenv,Cmdenv:Envir定义了表示用户接口的TOmnetApp基类,Tkenv和Cmdenv都是TOmnetApp的派生类。main()函数是Envir的一部分,为仿真决定选用合适的用户接口类,创建用户接口类的实例并执行。Sim和模型对ev对象的调用通过实例化TOmnetApp类进行。Envir通过TOmnetApp和其它类的方法实现Tkenv和Cmdenv的框架和基本功能。 2.2 OMNeT++的安装 OMNeT++的安装环境: a) Linux b) 其他一些类似Unix的系统 c) Win32 平台 (NT4.0, Window 2000,XP) 本书主要讨论在windows平台下的仿真。 2.2在windows环境下,安装前需要安装好Visual C++ 7.0或具有 Service Pack 6支持的Visual Studio 6.0; 安装步骤 (1)从官方网站:www.omnetpp.org下载omnetpp-3.3-win32.exe安装包,开始安装 (2)在安装过程中,注意vc++版本的选择。它有vc8.0、vc.7.0、vc6.0等选项,一般选择vc6.0 release。另外还有gswin32c路径的选择,ghostscript 主要用于打开或打印ps或pdf图形格式的文件。 (3)安装完毕后,应该先进行环境变量的设置。即:在vc6.0的bin目录下,找到VCVARS32.BAT文件,然后进入cmd下,运行即可 2.3 OMNeT++语法 OMNET++是面向对象的离散事件模拟工具,为基于进程式和事件驱动两种方式的仿真提供了支持。 OMNET++采用混合式的建模方式,同时使用了OMNET++特有的ned(Network Discription,网络描述)语言和C++进行建模。OMNET++的主要模型拓扑描述语言NED,采用它可以完成一个网络模型的描述。 网络描述包括下列组件:输入申明、信道定义、网络定义、简单模块和复合模块定义。使用NED描述网络,产生NED文件,该文件不能直接被C++编译器使用,需要首先采用OMNET++提供的编译工具NEDC将.NED文件编译成.cpp文件。最后,使用C++编译器将这些文件与用户和自己设计的简单模块程序连接成可执行程序。 2.3.1 NED语言 2.3.1.1 NED总概述 NED语言用来刻画定义模型的拓扑结构,方便对一个网络的模型化描述,这意味着一个网络的描述可以包括一组元件的描述(通道,简单/复杂模型),这些组件的描述可以在其他网络描述中得以重用。包含网络描述的文件带有.Ned的后缀,.Ned文件动态地载入到模拟程序,或者用Ned编译器或C++代码链接到模拟器执行。NED文件可以使用任何文本编辑器或GNED图形编辑器来编写。 2.3.1.2 Ned描述的组件 一个NED描述包括以下的组件(按任意次序排列) (1)输入指示:用于引进其它网络描述文件,引进一个网络描述后,可以使用它所包含的模块通道等组件,当一个文件被引进,只有声明信息是可用的,并且引进一个D文件并不会使该NED文件被编译,当父文件被NED编译时,例如,你可以编译所有并连接所有的网络描述文件你可以用文件名(用NED扩展名也可),同样可以在文件包括一条路径或者用NED编译器的
图2:OMNeT++中的简单模块和复合模块 2.3.1.3函数 在NED表达式中,可以使用以下数学函数: (1)C语言中<math.h>库函数:exp( ),log( ),cos( ),floor( ),ceil( )等等。 (2)产生随机变量的函数:uniform, exponential, normal等等。 表达式可以包含不同类型的随机变量,指针类型(除了const)返回不同的值,每次被计算。如果声明为const类型,指针值只在仿真开始的时候计算一次,以后的访问返回相同的值,随机变量程序用随机数字生成其中的一个,缺省为genertor0。 函数描述如下表:
(3)用户自定义函数: 要使用用户自定义的函数,需C++代码带有0,1,2,3,4个参数(double型),返回double型的值,函数要在C++文件中用Define_Function()macro注册。例如:
例: #include <omnetpp.h> double average(double a, double b) { return (a+b)/2; } Define_Function(average, 2); 2表示average()函数有2个参数。这样定义之后,average()函数就能用于NED文件了。 module Compound parameter: a,b; submodules: proc: Processor parameters: av = average(a,b); endmodule 如果参数类型不是double,可以进行类型转换。另外,简单模块类型名(#include 包含)要利用Define在OMNeT++中注册,允许类名与NED定义的简单模块名不相同。 2.3.2 简单模块 2.3.2.1 OMNET++中离散事件 一个离散事件系统是指一个系统的状态改变是离散的,在两个连续的事件之间没有任何事件发生。简单地说,事件规定了系统状态的改变,状态的修改仅在事件发生时进行。离散事件系统可以使用离散事件模拟进行仿真。例如,计算机网络通常被看作是离散事件系统。部分事件包括: 包传输的开始 包传输的结束 重传等待时间到达 事件发生的时间通常被称做事件时间戳,在OMNET中叫做到达时间。模拟时间或虚拟时间是指模拟程序运行了多长时间,相对地,真实时间或cpu时间是指多少cpu时间被消耗。 OMNET++使用消息来描述事件。每一事件都通过一个cMessage类或它的一个子类来表示;不存在单独的事件类。消息从一个模块发送到另一模块——这意味着“事件将发生”的地方就是消息的目的模块,事件发生的模拟时间就是消息到达时间。像“等待满期”事件通过模块给自己发送一个消息而实现。 OMNET++中的模拟时间被存储在C++类型的simtime_t中,是双精度类型。 2.3.2.2 包传输模型 (1) 延迟,比特误差率,数据速率 可以被三个指针赋值的连接便利了通信网络建模,对其他的模型来说也非常有用。 • propagation delay (sec) • bit error rate (errors/bit) • data rate (bits/sec) 所有的这些指针都是可选的,你可以为每一个连接指定链接指针,也可以定义链接类型,并在整个模型中使用。传播延迟是消息通过通道到达目的地的时间段,传播延迟以秒为单位计算。比特误差率对消息的传输有影响,比特误差率就是一个比特被错误传输的机率。 一条有N比特的消息,没有错误的比特长度: Pnobiterror = (1 − ber)length 消息有一个错误标志。 数据速率的单位bits/second。 消息的发送时间与传输的第一个比特和接收的最后一个比特相关。(图3)
图3 消息传输 上面的模型不能模拟所有的协议。在Token Ring 和 FDDI协议中,站发送重复的比特直到整个帧到达。如果一条消息沿途经过一系列的链接与模块。模块的行为好像每一个模块都在等待消息的最后一个比特到达,然后开始发送。因为上述的影响是不期望的,你更希望数据通过只有一个链接的路由来传播。 2.3.2.3 定义简单模块 (1) 直接或间接定义一个CSimpleModule的子类; (2) 以define_Module() 或define_Module_Like()宏注册之; a) 作用:声明一个simple module 类型并且建立与相应NED文件的关联。前者用于类名与NED定义的SimpleModule名相同,后者用于不同,可以为一个NED描述的SimpleModule提供不同的实现。 b) 每个SimpleModule都必须手动添加该宏,CompondMoudule由OMNeT自动添加。 (3) 实现模块类: 例子: #include <omnetpp.h> // module class declaration: class SlidingWindow : public cSimpleModule { Module_Class_Members(SlidingWindow,cSimpleModule,8192) //构造函数 virtual void activity(); }; // module type registration: Define_Module( SlidingWindow ); // implementation of the module class: void SlidingWindow::activity() { int windowSize = par(“windowSize”); … } 相关的NED文件 // file: swp.ned simple SlidingWindow parameters: windowSize: numeric const; gates: in: fromNet, fromUser; out: toNet, toUser; endsimple (4) 建立构造函数: 使用宏Module_Class_Members(classname, baseclass, stacksize); 如果你使用activity(),这个模块以协同模式执行, 需要划分出一个独立的栈空间。若栈空间为0使用handleMessage()。 需要变量参与初始化,需要自己重写构造函数。 2.3.2.4 简单模块中的主要成员函数 (1) activity() 它使得你可以象编写一个进程、线程一样编写一个简单模块。等待消息、延缓执行时间等等。拥有这个函数的简单模块们作为一系列协同程序协同执行,又称之为协同多任务。手动设置模块栈空间,一般为16k,如果模块存在递归或本地变量占空间较大的话,可以设置为更大的栈空间。 (2)handleMessage() a)为每个message / event调用handleMessage()。 b)你需要在initialize()函数中初始化变量,一些基于协同的函数如wait()、receive()等均不能调用。 c)SimpleModule的stacksize一定要设置为0。 在事件处理中被调用,对每一个简单模块,用户需重定义该函数。在其中可以使用一些消息相关函数send()(发送消息)、scheduleAt()(自发消息)、cancelEvent()(删除scheduleAt()的消息)。 (3)Initialize() 在初始化消息放入FES(Future Event Set)后,在执行前被调用,初始化成员变量。复合模块的初始化先于其子模块。 (4)Finish() 循环结束(FES没有模拟事件时)后正常中止时被调用,模块的调用顺序刚好与initialize()相反。 2.3.3 消息 2.3.3.1 cMessage类 cMessage是OMNET++的一个中心类。CMessage和子类的对象可以模拟一些东西:事件;消息;包;帧;蜂窝;网络中的位或信号传输;系统中的实体传输等等。一个cMessage对象有许多属性如下: a) 名字是string(const char *)类型,在模拟程序中自由使用。消息的名字出现在Tkenv(如:动画中)的许多地方,因此选择一个描述性的名字非常必要。这个属性继承于cObject。 b) 消息类型被假定为携带一些消息类型信息。0或正值可以被自由使用。负值被OMNET仿真库保留起来以供使用。 c) 长度被用于计算当消息经过一个赋值了数据速率的链接时的传输延时。 d) 位错误标记被仿真内核以1−(1−ber)length的概率设置为真,当消息被发送经过一个赋值了位错误率(ber)的链接的时候。 e) 优先权被仿真内核用来在具有相同到达时间值的消息队列(FES)中定制消息。时间戳不被仿真内核使用。 2.3.3.2 消息定义 假设你需要一些消息对象来携带源和目的地址,还有跳数。你可以写一个mypacket.msg文件如下: message MyPacket { fields: int srcAddress; int destAddress; int hops = 32; }; 消息子集编译器的任务是生成C++类,像允许Tkenv检查这些数据结构的“reflection”类一样,你可以从你的模型中使用。 如果你使用消息子集编译器来处理mypacket.msg,将创建以下文件:mypacket_m.h和mypacket_m.cc。mypacket_m.h包含Mypacket C++类的申明,field(域)中支持以下数据类型: (1) 原始类型:bool, char, short, int, long, unsigned short, unsigned int, unsigned long, double, string,一个动态定位string,以contst char *呈现。 (2) 结构,类(根于或不根于cObject),消息句法中或外部C++代码中被申明。 2.3.3.3 消息的收发 OMNeT中,模拟就是一系列简单模块间通过message进行通信。 (1)普通发送: send(cMessage *msg, const char *gateName, int index=0); send(cMessage *msg, int gateId); isBusy(); transmissionFinishes(); (2)广播和重传: 不能使用send()发送同一个message多次。 Why:消息就是一个具体的对象,它不能在同一时间出现在不同的地点。一旦被源端送出,该消息就不再属于源端,到达目的端后,目的端就有对该消息的一切控制权如转发、删除、接收。 (3)延迟发送: a)Wait();send(msg,”outgate”); b)sendDelayed(cMessage *msg, double delay, const char *gate_name, int index); c)sendDelayed(cMessage *msg, double delay, int gate_id); d)送出时间是当前的模拟时间+延迟时间; E.g:sendDelayed(msg, 0.005, “outGate”); (4)自传消息: 例如消息超时重传、job结束等等,这些源自于模块内部的事件,要由模块自身产生发给自己的消息来触发。 a)使用scheduleAt()发送自传消息; scheduleAt(absoluteTime, msg); scheduleAt(simtime()+delta, msg); b)和普通消息一样接收; c)isSelfMessage():判定是否自传消息; 例:实现timer cMessage *timeoutEvent = new cMessage(“timeout”); scheduleAt(simTime()+10.0, timeoutEvent); //… cMessage *msg = receive(); if (msg == timeoutEvent) { // timeout expired } else { // other message has arrived, timer can be cancelled now: delete cancelEvent(timeoutEvent);//从FES中删除 } 2.3.4 模块参数、门及连接的访问 2.3.4.1消息参数的访问 调用cModule 的par()成员函数可以访问模块指针: cPar& delayPar = par(“delay”); cPar类是一个存储值的对象,它支持数据类型,指针值可以这样读: int numTasks = par(“numTasks”); double processingDelay = par(“processingDelay”); 如果指针上一个随机变量或者指针值可以改变,最好存储指针的索引,在需要时重新读取指针的值。 cPar& waitTime = par(“waitTime”); for(;? { //… wait( (simtime_t)waitTime ); } 如果NED source 或者ini 文件对wait_time指针赋予了一个随机值,上面的代码导致不同的延时。指针值可以在程序中改变。如果指针通过索引引用,其他模块也可以看到它的变化。指针可以作为模块间通信的方法。 par(“waitTime”) = 0.12; Or: cPar& waitTime = par(“waitTime”); waitTime = 0.12; 2.3.4.2门和连接的访问 (1)简单模块门 门实现模块的连接。OMNET++支持单向的简单线路,因此有输入门输出门,消息从输出门发出,在输入门接收。门用标识符识别,以字母开头,支持门向量,一个门向量包含一组简单的门。门声明在gates关键字后,一个空括号[ ]表示门向量,向量元素个数从0开始。例: 当简单模块被用作复合模块类型的组件时,门向量的大小被给定,因此模块的每个实例都可以有不同大小的门向量。例: simple NetworkInterface parameters: //… gates: in: fromPort, fromHigherLayer; out: toPort, toHigherLayer; endsimple simple RoutingUnit parameters: //… gates: in: output[]; out: input[]; endsimple (2) 连接 ①一个连接: ·包含属性(延迟,比特出错率,数据速度)或使用命名的通道(频道); ·可能出现在一个for循环中(生成多重连接); ·可能是有条件限制的; ②通道 如果不指定通道,连接就没有传输时延,传播时延与比特出错率,例如: node1.outGate --> node2.inGate; 在这种情况下NED源必须包含有通道的定义,可以直接指定通道指针。 node1.outGate --> error 1e-9 delay 0.001 --> node2.inGate; ③循环连接 如果使用门向量与子模块,就可以在一种场合中使用多个连接,多连接用for循环创建。 for i=0…4 do node1.outGate[i] --> node2[i].inGate endfor; 上面循环连接在图4中刻画。
图4 循环连接 2.3.4.3门的传输状态 isBusy()成员函数返回门当前是否正在传输。 transmissionFinishes()返回传输结束的时间。例如: cMessage *packet = new cMessage(“DATA”); packet->setByteLength(1024); // 1K if (gate(“TxGate”)->isBusy()) // if gate is busy, wait until it { // becomes free wait( gate(“TxGate”)->transmissionFinishes() - simTime()); } send( packet, “TxGate”); 如果简单模块的输出门并没有与连接直接相连,那就需要检查路由中的第二个门是否繁忙。 if (gate(“mygate”)->toGate()->isBusy()) 如果数据率在仿真中改变,改变只影响后来发生的消息。 2.3.3.4连接的状态 isConnected()成员函数返回门是否连接。如果是输出门,则由toGate()成员函数返回连接的门。如果是输入门,成员函数为fromGate()。 cGate *gate = gate(“somegate”); if (gate->isConnected()) { cGate *othergate = (gate->type()==’O’) ?gate->toGate() : gate->fromGate(); ev << "gate is connected to: " << othergate->fullPath() << endl; } else { ev << “gate not connected” << endl; } 要明确输出门最终与哪个模块相连,你可以按照下面的路径进行: cGate *gate = gate(“out”); while (gate->toGate()!=NULL) { gate = gate->toGate(); } cModule *destmod = gate->ownerModule(); sourceGate() and destination-Gate()成员函数也可以方便地完成这项工作。 2.4 仿真过程 仿真执行文件是一个独立的程序,因此它可以运行在其他没有OMNET++或现存模型文件的机器上。当程序被启动,它就开始读配置文件(omnetpp.ini)。这个文件包含一些设置——控制仿真程序怎样执行,模型参数值等等。配置文件也能够规定一些仿真运行;最简单的情况下,它们一个接一个地被仿真程序执行。 仿真输出被写进数据文件中:vector矢量文件,scalar标量文件,和用户输出文件。OMNET提供了一个名为Plove的GUI工具来观察和绘制vector输出文件的内容。不期望人们单独地使用OMNET++对结果文件进行处理:输出文件是一种能被读进像Matlab或Octave的数学包格式的文本文件,或被输入像OpenOffice Calc,Gnumeric 或MS Excel的电子数据表。所有这些外部程序为统计分析和清晰可见提供了丰富的功能性。
仿真编译过程如下: nedtool *.ned ————————将ned文件编译成_n.cc文件。 opp_msgc .msg————————将.msg文件编译成_m.h和_m.cc文件。 opp_nmakemake -f -e cc(cpp)——对目录下的所有cc(cpp)文件进行编译,cc或cpp决定于你目录下的文件。若都有,必须都进行编译。之后生成Makefile.vc文件。 nmake -f Makefile.vc——————生成可执行文件 2.5 配置文件omnetpp.ini 该文件使得模拟程序得知将要仿真的网络,并通过该配置文件传递一些参数。 可分为以下几部分: 【General】——包含适应于所有模拟运行的常规设置和所有用户界面。 【Run 1】,【Run 2】,….——包含每一运行设置。这些部分可能包含任意在其他部分中被承认的实体。 【Cmdenv】——包含Cmdenv专门设置。 【Tkenv】——包含Tkenv专门设置。 【parameters】——包含在NED文件中没有赋值的模块参数值。 【OutVectors】——输出矢量的配置记录。你可以通过矢量名称和模拟时间来指定过滤。 2.6 结果分析工具 2.6.1 矢量描绘工具Plove Plove特征: Plove是描绘OMNET++输出矢量的一个便利的工具。每个矢量的线性跟轴边界,缩放比例,标题和标注一样被设置为最频繁的绘图选项。你可以点击一下将图保存到文件中去。在windows中,可以将图片复制到矢量格式的剪切板中,然后粘贴到其他的应用程序中。绘图之前先对结果进行过滤是可能的。过滤能够平均化,切断极限值,通过计算柱状形可进行密度估计等等。启动时,Plove自动读你有效目录下的.ploverc文件。这个文件包含了一般的应用设置,包括你创建的滤波器。 Plove使用: 首先,在左边方框中加载一个输出矢量(.vev)文件。可以通过点击中间的向右箭头将左方框内的矢量复制到右方框中。PLOT按钮将开始绘制右方框内所选 矢量。在windows下是这样选定的:shift+左键拖选定一个区域,ctrl+左击选定/取消选定个别项。要调整图形风格,改变矢量的标题或增加一个滤波器,点击 Options……按钮。这也适应于一些选定的矢量。左方框作为你正在运行的矢量存储器。你可以加载许多矢量文件,删除你不想处理的矢量文件,对它们重命名等等。这些改变不会影响矢量文件或磁盘。(Plove从不会自身修改输出矢量文件)在右方框内,如果你想要过滤矢量,你可以复制它们也可以保存最初的矢量文件。如果你为一个矢量设置了正确的选项,但是暂时还不想它留在右方框内,你可以把它送回左方框内存储起来。 2.6.2 标量工具Scalar 输出矢量捕获了仿真运行的瞬时行为。但是,为了比较多样的参数设置下的模型行为,输出标量更有用。 输出标量格式,使用recordScalar()函数调用来记录标量结果,通常来自模块的finish()方法,代码如下: void EtherMAC::finish() { double t = simTime(); if (t==0) return; recordScalar(“simulated time”, t); …………………………. } 相对应的输出标量文件(默认,omnetpp.sca)大致如下: run 1 “lan” scalar “lan.hostA.mac” “simulated time” 120.249243 scalar …………… ……………… run 2 “lan” scalar “lan.hostA.mac” “simulated time” 235.678665 […] 每次调用recordScalar()在文件中都产生一”scalar”行。另外,一些仿真运行可以将它们的结果记录到一个单个的文件中----进行比较,创建x-y绘图,等等。 2.7、结束语 本章详细介绍了OMNeT++这个优秀的网络仿真平台,它主要用于离散事件的模拟。OMNeT++相对其它网络模拟器来说,使用是较为简单,但其使用方法仍然有其特殊性和复杂性。本章详细介绍了如何使用OMNeT++,对其体系结构,编程语法以及建模过程都作了详细介绍与深入剖析。 OMNeT++作为传感器网络的仿真平台具有显著的优势,具体包括: OMNeT++支持用户组件库,实现了模块类型的灵活重用。 OMNeT++的面向对象特性,允许仿真内核提供基类的灵活扩展。 OMNeT++提供了图形化的网络编辑器和网络、数据流查看工具。 OMNeT++提供仿真类库和用户界面,支持输入/输出、仿真数据的图形化显示、随机数生成器、消息结构等。通过用户界面,可以跟踪调试仿真过程。 仿真环境采用C++语言开发,并采用自定义的配置文件omnetpp.ini进行配置定义。 OMNeT++在仿真802.11的MAC和Directed Diffusion协议时,比其他的网络仿真工具要快。
第三章 物理层仿真(信道) 3.1 UWB的基础知识 3.1.1 UWB信号的应用背景 传统的无线电技术都是基于载波的。但是将信号调制到载波上面,需要在发送端进行调制,接收端又要进行解调。这就增加了硬件的复杂度。而且发射载波需要较大的功耗。这对移动终端的移动性能造成了极大的影响。在这些背景之下,美国联邦通信委员会(FCC)定义民用的UWB技术。UWB技术是一种与传统技术有很大不同的无线通信技术,其特点是低功耗、高带宽、低复杂度。超宽带技术解决了困扰传统无线技术多年的有关传播方面的重大难题,它具有对信道衰落不敏感、发射信号功率谱密度低、安全性高、系统复杂度低,能提供数厘米的定位精度等优点。UWB尤其适用于室内等密集多径场所的高速无线接入和军事通信应用中 3.1.2 UWB信号的定义 信号带宽大于500MHz,或信号带宽与中心频率之比大于25%为超宽带;信号带宽与中心频率之比在1%~25%之间为宽带,小于1%为窄带,可见UWB的带宽明显大于目前所有通信技术的带宽。见下图:
信号的相对带宽定义如下
如果相对带宽 大于0.20~0.25,我们就认为它是一个超宽带信号。 超宽带的传输方式一般是无载波的。传统的"窄带"和"宽带"都是采用无线电频率(RF)载波来传送信号,载波的频率和功率在一定范围内变化,从而利用载波的状态变化来传输信息。相反的,超宽带以基带传输。实现方式是发送脉冲无线电(IR)信号传送声音和图像数据,每秒可发送多至10亿个代表0和1的脉冲信号。这些脉冲信号的时域极窄(0.1至1.5纳秒),频域极宽(数Hz到数GHz,可超过10GHz),其中的低频部分可以实现穿墙通信。信号的发射功率都十分低,仅仅相当于一些背景噪音,不会对其他窄带信号产生任何干扰。由于UWB系统发射功率谱密度非常低,因而被截获概率很小,被检测概率也很低,与窄带系统相比,有较好的电磁兼容和频谱利用率。此外,传统的无线通信在通信时需要连续发出载波(电波),要消耗不少电能。而UWB是发出脉冲电:直接按照0或1发送出去。由于只在需要时发送出脉冲电波,因而大大减少了耗电量(仅为传统无线技术的1/100)。UWB信号的功率谱密度极低,发射系统比现有的传统无线电技术功耗低得多;数据传输速率极高;非常可贵的对其他无线系统的低干扰特性;很强的抗多径干扰能力;比构造扩频系统简单,成本低。 IEEE802.11a Bluetooth UWB 传输速率 54Mbps 小于1Mbps 可高达500Mbps 通信距离 10m-100m 10m 小于10m 发射功率 1瓦以上 1毫瓦-100毫瓦 1毫瓦以下 应用范围 无线局域网 计算机等家庭和办公室设备互连 近距离多媒体 表1 :各种无线技术的参数比较 3.1.3 UWB的脉冲生成方式(高斯脉冲,非高斯脉冲) 传统的UWB信号是用高斯脉冲来产生。一个高斯脉冲的时域波形如下:
也可以用它的导数来产生波形,一般用上式的一阶或者二阶三阶导数来产生超宽带脉冲(参看文献[10],[11])。 3.1.4 UWB的调制方式 A :直接序列扩频UWB (DS-UWB) B :跳时超宽带 (TH-UWB) C :多频带超宽带 在文献[1]中采取了TH-PPM的调制方式。有关的这种调制的知识可以参考该文献。这里采用TH-PAM调制,也就是跳时码+二进制反极性调制。因为二进制反极性码具有比PPM码更好的性质。在相同的误码率要求下,二进制反极性信号的发射功率只是二进制正交PPM调制的一半。文献[10],[11]).因此可以采用TH+PAM的调制方式。 这种调制方式如图所示:
在这种调制中,时间被分为一个个帧,每个帧的时间为,每个帧由个时隙构成,在这里=7。每个时隙时间为。因此=。一个信息比特用个脉冲表示,在这里=4。一个用户在一个帧内只发送一个脉冲。因此用户A的一个信息比特要在上图的四个帧发送完后才完全接收。 在上图可以看到,用户A 的TH码是{0 1 5 3},因此,用户A在第一个帧的0时隙检测,为了区分信息比特,在这里采用了二进制反极性PAM调制。这里第一个帧内检测到的脉冲为-1。同理,接收机在第二个帧的第1时隙,第三个帧的第5时隙,第四个帧的第3时隙分辨检测,得到的信号分别为+1,+1, -1。至此,A用户的一个信息比特所代表的脉冲全部检测完成,为{-1 +1 +1 -1} 根据超宽带信号的信号特点,假设系统中有N个用户,那么链路i的信噪比的公式如下所示(文献[1]): 1.1 其中, N是活动链路的个数. 为一个个帧的时间 为第i链路的比特信息速率 是从发送节点j到接收节点i的链路增益. 是第i链路的接收节点的背景噪声的功率谱密度. 是UWB脉冲的形成因子 一个比特用个脉冲来表示,一个帧的时间为,这样一个比特完全发送完毕用的时间为= 这样,比特速率为: = 1.2 平均功率. 1.3 其中,为第i条链路在一帧内发送的功率,为第i链路的一个脉冲的能量。因此,为了改变i链路的功率,可以通过改变脉冲的能量,或者改变来实现.其中的改变又可以分为改变或者来实现。 3.1.5 用功率控制多址接入方法来进行链路的建立控制 在建立链路时候的多址接入方式采用了一种改进的的基于MEI 的分布式功率控制信道接入算法。本文考虑的网络拓扑是ad-hoc形式的网络,.各条链路之间不需要同步,仅仅需要接收点和发送点之间的同步即可。对于发射功率的上界,由于UWB工作的频段都是未经授权的频段,如果UWB的发射功率不经控制的话,很有可能对别的系统造成干扰。所以我们必须考虑发射功率的上界。在这里采用FCC的规定。 为了方便描述功率控制接入算法,这里首先引入”最大能忍受干扰”(MEI)的概念。 ”最大能忍受干扰” 也就是一个链路为了保持正常的通信QoS而能忍受的最大的额外的干扰。.假如链路i 的目标最小信噪比为,根据超宽带信噪比公式1.1,得下面的计算MEI的公式: 1.4 每个链路,定时检测自己周围的干扰情况,以及自己的,等信息,通过上式就可以算出自己的。 当一条新的链路要建立的时候,假设此时系统中已经有N个活动的链路,每个链路都已经算出自己的,并且定时将这些信息通过广播信道发送出去。 当一条新的链路(假设是第N+1条链路)要建立时,它首先监听出所有的链路的,.假设第N+1条链路的信噪比和速率要求分别为:和,由: 1.5 可以算得为了保证以速率发送,最小的功率要求为 1.6 但是,前面已经知道,系统中已经存在N条活动的链路,这些链路的优先级是比新链路高的,于是,第N+1条链路建立的时候,必须保证已经存在的链路的正常通信。于是第N+1条链路首先计算出
并且考虑到FCC的功率掩蔽,得出外界对N+1链路的功率上界限制为:
对比 与 如果,则可以成功建立新链路。 否则无法建立新链路,进入等待状态。经过一个随机的实验后再进行接入尝试。这样如果经过一个time out以后还无法建立,则N+1链路会将该问题交给高层处理,或者降低速率的要求。 当=时,显然链路N+1以发送。 当<,最佳的发送功率是。为了计算出,文献[1]中设计了一种平衡的MEI算法。但是该算法过于复杂。为了简单性,我们采取了一种折中的办法,取.经仿真,这种方法也可以得到很好的效果。 3.2 用OMNeT++对UWB进行仿真 3.2.1 算法仿真的概述 OMNeT++是一个强大的网络仿真器件,关于它的使用说明,请参看安装目录doc/下面usman手册。在这里我们介绍一下如何用它来进行物理层的UWB功率控制算法的仿真。在进行功率控制算法的设计的时候,必须考虑UWB的信号模型,干扰模型,多径传播模型,自由空间传播模型等等。仿真的结果如下图所示:
图 1 仿真的网络拓扑图 这个程序主要包括两个部分,第一个部分是自定义的一个message类powerMsg,用来携带功率控制的信息,如功率大小,链路增益,信噪比,节点的状态信息(空闲或者忙碌)等等。第二个部分就是实现具体算法的详细代码。仿真的输出如下图所示:
图2 仿真的输出:
3.2.2 算法的具体流程 发送节点: 1.初始化一个链路建立请求,并发送请求信息给RX节点。 2.计算本节点在不干扰现存的其它链路的情况下,能运用的最大发送功率Pmax。 3.如果收到RX节点的应答最小功率要求Pmin,则进入4,否则等待。 4.进行比较,如果Pmin≤Pmax,则说明这条链路可以成功建立,转入5,否则链路无法建立,经过一个随机的时间后,返回1的初始化步骤。 5.计算最优功率Popt,并发送给链路成功建立的状态信息给RX节点。 6.开始发送数据。 接收节点: 1.检查自己的接收缓冲,如果收到TX的初始化链路请求信息,则转入2,否则返回1。 2.计算出自己为了维持所需的信噪比,需要的最小功率Pmin。并发送给TX。 3.如果收到TX的成功建立链路的信息,则进入4。否则返回1。 4.开始接收数据。 这个算法的流程图如下图所示:
图3 算法的流程图 3.2.3 算法的主要代码 基于活动链路保护的UWB功率控制算法的主要步骤在上面的流程图中已经详细给出,下面将给出这个算法的主要代码。 正如前面所述,这个程序主要包括两个部分,第一个部分是自定义的一个message类powerMsg,第二个部分就是实现具体算法的详细代码。下面的是这个两个部分的主要代码。 第一部分: 自定义的一个powerMsg类,用来携带功率控制的信息,如功率大小,链路增益,信噪比,节点的状态信息(空闲或者忙碌)等等。 message powerMsg { fields: double txPower; double rxWantMinpower; double MEI; int sourceNodeIndex; int destintNodeIndex; // 如果为 -1说明是广播的. int x; //记录发送节点的x,y位置 int y; bool wantCommWithYou=false; bool linkSucessToCreat=false; //如果成功建立一个link则为true } 这个msg文件通过编译后,会生成另外两个文件:powerMsg_m.h和powerMsg_m.cc。 第二:建立一个物理层的模块。 物理层的模块主要处理与信道有关的东西,比如功率控制的信道接入方式,维持链路的增益,计算比特误码率等等。 #include <omnetpp.h> #include “powerMsg_m.h” class Physic :public cSimpleModule { private: double initxPower; double commonPower; //发起链接请求的时候的通用的功率 double deviceMaxpower; double myMEI; double mySIR; double myLinkGain; double requireRate; double totoalInterferenc; double backnoise; double shapingFactor; double tagetSIR; double Tf; int posX, posY; int targetNodeIndex; int sourceNodeIndex; bool isBusy; cArray txpmsgArray; cArray rxpmsgArray; public: double getDistance(powerMsg * pmsg); double getGain(powerMsg *pmsg); double getInterference(); double getSIR(); double getmyMEI(double tSIR); void sendmyMEI(); void sendIniPmsg(int targetNodeIndex); double maxPower(); double minPower(); double resetPower(); virtual void initialize(); virtual void handleMessage(cMessage * msg); virtual void finish(); };
#include “Physic.h” Define_Module(Physic);
void Physic::initialize() { //从父模块(就是node节点模块那里获取x,y的位置) posX=(int)parentModule()->par(“x”); posY=(int)parentModule()->par(“y”); txpmsgArray.setName(“txpmsgList”); rxpmsgArray.setName(“rxpmsgList”); isBusy=false; myLinkGain=0.0; initxPower=0.0; //发起链接请求的时候的通用的功率 commonPower=1.0; deviceMaxpower=2.0; requireRate=1.4e6; backnoise=4.0e-18; shapingFactor=1.99e-3; tagetSIR=10.0; Tf=1.0e-7; ev<<"the node’s Physic layer… is initializing…the node is "<<parentModule()->index()<<endl; ev<<“send a msg to MAC”<<endl; //发一个Msg到MAC层 cMessage *toMacmsg=new cMessage(“toMAC”); send(toMacmsg,“toMAC”); //一条新的链路的初始化请求 if(((parentModule()->index())%2)==0) { ev<<“the node “<< parentModule()->index()<<” is initializing… and want to send a selfMsg…”<<endl; simtime_t waittime=(simtime_t)uniform(0.10,3.0); cMessage *smsg=new cMessage(“selfMsg”); scheduleAt(simTime()+waittime,smsg); ev<<“self msg had send…”<<endl; isBusy=true; } }
//处理cMessage的主要代码 void Physic:: handleMessage(cMessage * msg) { if (msg->isSelfMessage()) { //处理selfMsg; delete msg; int a=genk_intrand(0,11); while((a%2)==0) { a=genk_intrand(0,11); } bool aIsBusy=false; for (int i=0;i<rxpmsgArray.items();i++) {
powerMsg *pmsgs=(powerMsg *)rxpmsgArray[i];
if(pmsgs->getSourceNodeIndex()==a)
{
aIsBusy=true;
ev<<" the node a is now receiving ,choose a new one "<<endl;
break;
}
}
if(aIsBusy)
{
ev<<"the node "<< parentModule()->index()<<" is initializing.... and want to send a selfMsg..."<<endl;
ev<<"but the destination node is busy...choosing aother one ....."<<endl;
cMessage *smsg=new cMessage("selfMsg");
scheduleAt(simTime(),smsg);
ev<<"self msg had send..."<<endl;
isBusy=true;
}
else
{
ev<<"I am the node "<<parentModule()->index()<<" the self msg had arrived...."<<endl;
ev<<" and i want to comunicate with the node "<<a<<" and send a msg to him...... "<<endl;
sendIniPmsg(a);
isBusy=true;
}
}
else if (strcmp(msg->name(),"txpowerMsg")==0)
{
powerMsg *pmsg=(powerMsg *)msg;
ev<<" a txpowerMsg is arriving from the node "<<pmsg->getSourceNodeIndex()<<endl;
ev<<"convert to pMsg.....I am the node "<<parentModule()->index()<<endl;
if ((pmsg->getWantCommWithYou())&&((pmsg->getDestintNodeIndex())==(parentModule()->index())))
{
if (!isBusy) {
if(pmsg->getLinkSucessToCreat()) //此时已经成功建立了连接
{
ev<<"I am the node "<<parentModule()->index()<<" an the link is sucess to creat.."<<endl;
powerMsg *minpmsg=new powerMsg("rxpowerMsg");
minpmsg->setSourceNodeIndex(parentModule()->index());
minpmsg->setDestintNodeIndex(-1); //广播MEI了
minpmsg->setWantCommWithYou(false);
myLinkGain=getGain(pmsg);
initxPower=pmsg->getTxPower();
myMEI=getmyMEI(tagetSIR);
minpmsg->setMEI(myMEI);
minpmsg->setLinkSucessToCreat(true);
minpmsg->setX(posX);
minpmsg->setY(posY);
isBusy=true;
for(int i=0;i<11;i++)
{
powerMsg *copypmsg= (powerMsg *)minpmsg->dup();
send(copypmsg,"toOutside",i);
}
bubble("I have broadcasted my MEI...");
ev<<"I am the node .."<<parentModule()->index()<<"I have broadcasted my MEI..."<<endl;
if(isBusy)
{
//在这个link的生命内 不停的广播自己的MEI myMEI=getmyMEI(tagetSIR);
ev<<" In my life I am Broadcasting........my MEI is :"<<myMEI<<endl;
powerMsg *minpmsg=new powerMsg("rxpowerMsg");
minpmsg->setSourceNodeIndex(parentModule()->index());
minpmsg->setDestintNodeIndex(-1); //说明要广播MEI了
minpmsg->setWantCommWithYou(false);
minpmsg->setMEI(myMEI);
minpmsg->setLinkSucessToCreat(true);
minpmsg->setX(posX);
minpmsg->setY(posY);
for(int i=0;i<11;i++)
{
powerMsg *copypmsg= (powerMsg *)minpmsg->dup();
send(copypmsg,"toOutside",i);
}
}
}
else
{
myLinkGain=getGain(pmsg);
double minipower=minPower();
ev<<"I am the node "<<parentModule()->index()<<" computing the min power is OK!!"<<endl;
ev<<"the min power is .."<<minipower<<endl;
powerMsg *minpmsg=new powerMsg("rxpowerMsg");
minpmsg->setSourceNodeIndex(parentModule()->index());
minpmsg->setRxWantMinpower(minipower);
minpmsg->setDestintNodeIndex(pmsg->getSourceNodeIndex());
minpmsg->setWantCommWithYou(true);
minpmsg->setX(posX);
minpmsg->setY(posY);
for(int i=0;i<10;i++)
{
powerMsg *copypmsg= (powerMsg *)minpmsg->dup();
send(copypmsg,"toOutside",i);
}
send(minpmsg,"toOutside",10); // 是广播
}
}
}
else if( ((pmsg->getDestintNodeIndex())!=(parentModule()->index()))&&(pmsg->getLinkSucessToCreat()))
{
bool haveOldmsg;
haveOldmsg=false;
for (int i=0;i<txpmsgArray.items();i++)
{
powerMsg *pmsgs=(powerMsg *)txpmsgArray[i]; if(pmsgs->getSourceNodeIndex()==pmsg->getSourceNodeIndex())
{
haveOldmsg=true;
delete txpmsgArray.remove(i);
int index =txpmsgArray.add(pmsg);
break;
}
}
if(!haveOldmsg)
int indexs=txpmsgArray.add(pmsg);
}
}
else
{
delete pmsg;
ev<<"I am the node "<<parentModule()->index()<<“I receive a msg which is usefulless …deleting it”<<endl; } }
else if (strcmp(msg->name(),"rxpowerMsg")==0)
{
powerMsg *pmsg=(powerMsg *)msg;
ev<<"convert to pMsg....."<<endl;
if ((pmsg->getWantCommWithYou())&&((pmsg->getDestintNodeIndex())==(parentModule()->index())))
{
//是rx应答的信息,里面有它需要的最小的power.
double rxminpower=pmsg->getRxWantMinpower();
ev<<"my min power is :"<<rxminpower<<endl;
double txmaxpower=maxPower();
if (rxminpower>txmaxpower)
{
isBusy=false;
while(!isBusy)
{
ev<<"the node "<< parentModule()->index()<<" re transmiting is
want to send a selfMsg…"<<endl; simtime_t waittime=(simtime_t)uniform(0.10,3.0); cMessage *smsg=new cMessage(“selfMsg”); scheduleAt(simTime()+waittime,smsg); ev<<“self msg had send…”<<endl; }
}
else
{
initxPower=(txmaxpower+rxminpower)/2;
isBusy=true;
powerMsg *txpmsg=new powerMsg("txpowerMsg");
ev<<"I am the node "<<parentModule()->index()<<"sucees to contruct a new link ..."<<endl;
ev<