1.一种基于TCP协议的NS3与MATLAB集成的联合仿真接口方法,含有以下步骤:
(1)在linux操作系统中搭建两种仿真软件的运行环境;
步骤11:选择合适的linux操作系统;
由于NS3的运行环境为linux操作系统,选择基于Debian GNU/Linux发行版的计算机操作系统Ubuntu运行NS3,Ubuntu拥有良好的可视化界面和便于软件开发的IDE,选择安装的Ubuntu操作系统版本为16.04LTS;
步骤12:安装MATLAB 2011b UNIX版;
步骤13:安装NS3网络仿真软件;
NS3是一个离散事件网络仿真软件,由仿真核心和模型组成,并以C++程序实现,NS3被构建为一个C++库,可以静态或动态地链接到定义模拟拓扑并启动仿真软件运行C++外部脚本主程序,以此来运行该网络的仿真,选择安装的NS3版本为3.27;
步骤14:安装C++版本的Eclipse并对NS3进行配置;
由于NS3本质为一个C++库,所以为了能够方便的对NS3中的程序进行修改,安装了C++版本的Eclipse,并对NS3进行相关配置,配置步骤如下:1.在Eclipse中新建C++的空项目工程,将ns-3.27文件夹拷贝到这个工程下面,刷新项目并需要先在Eclipse终端中运行NS3配置指令./waf configure;2.右键工程,选择属性(properties),在build中(C++Build)进行修改,选择builder类型为External builder,build命令选择编译NS3程序的waf编译器所在的路径,比如:${workspace_loc:/ns3/ns-3.27/waf},其中:workspace_loc是指工程所在的路径;build的目录就选择NS3对应的目录,比如:${workspace_loc:/ns3/ns-3.27/build};3.外部编译器的配置,也就是NS3使用的waf编译器的配置,在运行--外部工具--外部工具配置(run-external tools-external tools configurations)中,位置(location)是waf的路径,比如:${workspace_loc:/ns3/ns-3.27/waf},工作目录(working derectory)是NS3所在的目录,比如:${workspace_loc:/ns3/ns-3.27},自变量(arguments),填:--run"${string_prompt}",用户输入执行的C++文件,就是输入的参数;
4.调试:NS3可以使用Eclipse调试,也可以使用通过终端调用gdb来进行调试,在Eclipse中调试步骤为:右键工程,选择属性(properties),选择调试的配置(run/debug settings),创建一个新的加载配置,选择你刚刚生成的文件project,选择环境设置栏(environment),新建一个环境变量,变量名(name)为:LD_LIBRARY_PATH,变量值(value)为build的路径:${workspace_loc:/ns3/ns-3.27/build},之后选择Main栏,在C/C++Application中填写build文件夹下需要调试的已经通过编译器waf成功的目标文件,比如:build/scratch/third,另外也可已通过gdb来调试NS3程序,在ns-3.27文件夹中打开终端,输入指令比如:$./waf--run=hello-simulator--command-template="gdb%s--args
(2)通过TCP协议建立两个仿真软件之间的交互接口;
步骤21:在MATLAB和NS3中编写了客户端和服务器程序,其中MATLAB作为主控方,NS3作为被控方,仿真软件的交互是通过使用Berkley的TCP协议完成的,两个仿真软件之间通过TCP协议套接字建立连接,使其为两个仿真软件间通信IPC提供强大和灵活的机制,因此在MATLAB和NS3编写的程序中都创建了一个新的TCP套接字,创建套接字的代码为socket=socket(AF_INET,SOCK_STREAM,0);之后两个仿真软件轮流作为数据处理方,仿真运行的主体在两者之间反复切换,通过这种交替运行的方式驱动仿真;
步骤22:对于MATLAB的客户端的套接字,在数据传输之前,使用connect函数用于客户端套接字对NS3发起会话连接功能,代码为connect(sock,(struct sockaddr*)&addr,sizeof(addr),sock为MATLAB客户端创建的TCP套接字,addr是一个结构体指针,其中包括目标服务器的地址和端口号,其中目标服务器IP为0x7f000001,即本机的IP地址(由于NS3服务器也运行在同一台计算机上),发送目标服务器端口号为3425(NS3服务器占用该端口);
步骤23:使用NS3的服务器套接字,具体包括:S1.首先套接字使用使用bind函数与选定域中的地址绑定,代码为bind(listener,(struct sockaddr*)&addr,sizeof(addr)),listener为服务器创建的TCP套接字,addr包括服务器的侦听的客户端地址和服务器的端口号,其中侦听的客户端地址为htonl(INADDR_ANY),即服务器侦听所有客户端发起的会话请求,端口号为3425,S2.使用listen函数用于服务器套接字开启侦听会话连接功能,代码为listen(listener,1),等待连接队列的最大长度设为1,S3.当服务器侦听到合适的请求,使用accept函数接受客户端发起的会话连接,并创建一个用于与MATLAB客户端进行数据传输的新套接字,如果该函数调用还处于监听模式socket之下,原始套接字可以提供其他连接;
步骤24:在客户端和服务器中都使用send数据发送函数和recv数据接收函数,当会话连接被服务器接收后,即可开始数据包的传输,传输的数据类型为被指定的结构体,该结构体可以根据两个仿真软件之间需要交互的信息而具体更改,初步包括了节点的发送触发信号和接收感知信号在该结构体中,(3)改进NS3的默认仿真器;
步骤301:为了能够让MATLAB外部驱动NS3仿真,通过继承DefaultSimulatorImpl类创建了一个新仿真器类ExternallyDrivenSim,在NS3中,默认情况下只有两种时间关系:实时(所有仿真事件在现实生活中随着时间的推移而发生的)与默认时间(所有的仿真事件随着时间的推移尽可能快地发生在计算机上),由于NS3是离散时间仿真软件,当选择默认时间仿真方式,仿真在实验的极限下不可能在必要时刻停止,在这个项目的框架中,使用的是默认时间的仿真方式,该方式通过调用NS3核心模块的默认仿真器DefaultSimulatorImpl类来实现,为了保有默认仿真器DefaultSimulatorImpl类的全部功能,通过继承DefaultSimulatorImpl类来创建新的仿真器类ExternallyDrivenSim,之后将会在新类中增添和修改相应的辅助函数,他们将会有助于该新仿真器类的实现;
步骤302:在NS3中的默认仿真器中添加了自定义仿真时间控制模块,该模块允许仿真在每次以前给出过的时间上限时停止,该模块在ExternallyDrivenSim::Run公有函数中实现,通过将现有的仿真事件的时间和在ExternallyDrivenSim类定义的私有属性m_TimeLimit比较,只要仿真事件的时间小于等于m_TimeLimit,那么将会调用私有函数ProcessOneEvent的仿真,当仿真事件的时间大于m_TimeLimit,将会将m_TimeLimit增加一定步长的时间来更新m_TimeLimit的时间上限,之后与MATLAB客户端进行交互发送给MATLAB该段仿真的结果和并从MATLAB接收下一段仿真的节点数据包发送计划,之后再运行新计划仿真事件,通过不断重复该过程,使得仿真时间以及仿真事件受到一定控制的往前推进;
步骤303:创建ExternallyDrivenSim::ExternallyDrivenSim构造函数,它将会在仿真开始前创建ExternallyDrivenSim类对象,在该函数中初始化时间上限m_TimeLimit为0,之后按照NS3的服务器套接字初始化的步骤建立与MATLAB客户端的连接,之后初始化结构体为空;
步骤304:创建ExternallyDrivenSim::Listen私有函数,用于在数据传输阶段通过使用新套接字调用recv函数来接收MATLAB发送过来的数据包通知结构体,并对结构体中的数据进行解析,根据解析的结果使用RunScheduleTransmit函数计划下一次的发送事件;
步骤305:创建ExternallyDrivenSim::GetEventId公有函数,在函数ProcessOneEvent中调用,用于获得正在执行仿真事件的EventId对象的所有信息;
步骤306:创建ExternallyDrivenSim::TransmitNotices公有函数,用于在数据传输阶段让新套接字使用send函数向MATLAB发送数据包通知结构体(NS3的UDP服务器的运行结果);
步骤307:创建ExternallyDrivenSim::Stop公有函数,作为父类对应同名函数的重写(覆盖)函数,当ExternallyDrivenSim::Stop公有函数在NS3仿真脚本中调用时,ExternallyDrivenSim::Stop公有函数将会设定ExternallyDrivenSim类的仿真结束事件的时间属性m_TimeOfEnd,NS3仿真运行时调用的ExternallyDrivenSim对象将会在对应时间计划仿真结束事件来结束整个仿真过程;
步骤308:创建ExternallyDrivenSim::GetNotices公有函数,作为静态函数,使其能够在UDP服务器应用类中调用,ExternallyDrivenSim::GetNotices公有函数收集UDP服务器的运行结果,以此生成将要发送给MATLAB的数据包通知结构体;
步骤309:创建ExternallyDrivenSim::SetEventId公有函数,作为静态函数,使其能够在UDP客户端应用类中调用,ExternallyDrivenSim::SetEventId公有函数将GetEventId函数获得所有事件信息强制类型转换为EventId变量,并返回该EventId变量给EventId类对象m_sendEvent,使得节点服务器知道现在运行的发送事件的EventId,通过该EventId判断正在运行的发送事件是否已经过期;
步骤310:创建ExternallyDrivenSim::GetClient公有函数,利用节点的智能指针返回该节点上UDP客户端应用的普通指针,之后通过该指针调用UdpEchoClientNew::ScheduleTransmit函数计划UDP客户端的发送事件;
步骤311:创建ExternallyDrivenSim::RunScheduleTransmit公有函数,在ExternallyDrivenSim类中调用ExternallyDrivenSim::GetClient函数来获取每个节点上UDP客户端应用的指针,之后通过该指针调用UdpEchoClientNew::ScheduleTransmit函数计划UDP客户端的某次发送事件,计划发送事件的时间与当前NS3仿真运行时间的差值将作为该函数的参数,这里设置为0秒即当前仿真时间执行发送事件;
步骤312:创建ExternallyDrivenSim::ProcessOneEvent私有函数,作为父类对应同名函数,它隐藏了父类同名函数,ExternallyDrivenSim::ProcessOneEvent私有函数用于将事件队列中的事件提取出来并从事件队列中移除,并推进该事件的仿真;
步骤313:创建ExternallyDrivenSim::Run公有函数,作为父类对应函数的重写(覆盖)函数,在ExternallyDrivenSim::Run公有函数中ExternallyDrivenSim类将会调用ProcessOneEvent函数运行所有被计划的事件,并通过使用如步骤302所述的仿真时间控制模块推进仿真;
步骤314:创建ExternallyDrivenSim::SetSimulationStep公有函数,在仿真脚本中被调用,用于设定每次属性m_TimeLimit的时间上限的增加量g_SimulationStep,属性g_SimulationStep作为NS3仿真的周期步长,每当当前仿真时间超过m_TimeLimit时,m_TimeLimit将加上g_SimulationStep作为新的m_TimeLimit;
步骤315:创建ExternallyDrivenSim::DoDispose公有函数,作为父类对应函数的重写(覆盖)函数,ExternallyDrivenSim::DoDispose公有函数会隐式的析构ExternallyDrivenSim类对象,并关闭用于传输数据的新套接字和侦听请求的原始套接字;
(4)改进NS3的UDP应用程序类以及其助手类;
步骤41:设计两个新的类,即UdpEchoClientNew类和UdpEchoServerNew类,来配合ExternallyDrivenSim类运行,默认的UDP应用程序类包括了两个类,分别为UdpEchoClient类和UdpEchoServer类,这两个类适用于在使用默认仿真器DefaultSimulatorImpl类的情况,对于ExternallyDrivenSim类它们缺乏相应的功能因此无法有效的运行,因此通过继承这两个类,设计了两个新的类UdpEchoClientNew类和UdpEchoServerNew类来配合ExternallyDrivenSim类运行;
步骤42:创建UdpEchoClientNew::StartApplication函数,作为父类对应函数的重写(覆盖)函数,对于UdpEchoClientNew::StartApplication函数的修改如下:T1.不需要在应用程序启动时传输第一个数据包,因此不再需要启动函数调用:ScheduleTransmit(Seconds(0.));T2.新的应用程序不需要等待服务器回复,因此不再需要启动函数调用:m_socket->SetRecvCallback(MakeCallback(&UdpEchoClientNew::HandleRea d,this));
步骤43:创建UdpEchoClientNew::send函数,作为父类对应同名函数,它隐藏了父类同名函数,其功能为使UDP客户端执行数据包发送事件,对于UdpEchoClientNew::send函数的修改如下:首先当UdpEchoClientNew::send函数运行时,没有必要计划新的事件,函数调用ScheduleTransmit(m_interval);不再被使用;其次每次调用此函数时,将ExternallyDrivenSim::SetEventId的值分配给变量m_sendEvent,该变量将会用来判断当前发送事件是否已经过期;
步骤44:创建UdpEchoServerNew::HandleRead函数,作为父类对应同名函数,它隐藏了父类同名函数,UdpEchoServerNew::HandleRead函数功能为使UDP服务器端处理发送从客户端发送过来的数据包,启动静态函数调用ExternallyDrivenSim::GetNotices,用于将该使UDP服务器接收到的数据包生成发送给MATLAB的通知,同时.新的应用程序也不需要发送服务器回复数据包;
步骤45:创建这两个新类的构造函数和析构函数,函数体为空;
步骤46:通过继承创建了新的类UdpEchoNewHelper,是NS3的应用程序助手类UdpEchoHelper的子类,通过UdpEchoNewHelper类可以在仿真脚本中安装以上两种新的应用程序类,UdpEchoNewHelper类的修改主要是将相应的助手类的类名进行替换为新类的助手类的类名,以及通过带参构造函数继承的方式创建UdpEchoNewHelper类中的构造函数。