1 NS3简介
1.1 NS3的定义说明
NS-3是一款离散事件驱动的网络仿真器,主要应用于研究和教育领域,旨在满足学术和教学的需求。
NS-3项目是一个完全开源的开发工程,于2006年启动。第一个版本发布于2008年6月,目前最新的版本为ns-3.15。
NS-3的适用平台包括:Linux、Mac OS、Cygwin(Windows下的仿Linux环境)。
1.2NS3和NS2的不同
NS-3并不是NS-2的扩展,而是一个全新的模拟器。
NS-2采用C++和OTcl代码编写,在学习过程中需要学习新的OTcl语言。而NS-3全部采用C++语言编写,并且带有可选择性的Python语言绑定,用户可以选择C++或者Python语言编写脚本代码,使用起来更加灵活。
NS-3并不支持NS-2的API。虽然NS-2中的一些模块已经移植到了NS-3中,但NS-3远没有NS-2完善。在NS-3开发的过程中,NS-3项目组会继续维护NS-2,同时也会研究过渡和整合机制。NS-3并不包含目前所有NS-2的功能,但它具有某些新的特性,如:正确的多网卡处理、IP寻址策略的使用、更详细的802.11模块等等。
1.3 获取更多NS-3资源的方式
用户可以从以下几个网站获取更多有关NS-3的详细资料:
1.主站点位于http://www.nsnam.org,提供NS-3系统的基本信息。
2.http://www.nsnam.org/ns-3-dev/documentation/该页面主要包括以下主要资料:
Tutorial: 初步介绍NS-3的相关知识,以及下载及安装,简单使用。
Manual:更深一步讲解NS-3的相关知识以及NS-3的编码风格。
Model Library: 主要介绍NS-3的相关模块。用户可以选择自己实际需要的模块学习,不需要全部看。
3.http://www.nsnam.org/doxygen/index.html该页面上提供了NS-3系统架构的更为详细的信息。在编写自己的模块时,查询类的成员函数,类的属性等,要经常用到这个链接。
4.维基百科网页http://www.nsnam.org/wiki可以作为NS-3主站点的补充。
5.NS-3的源码可以在http://code.nsnam.org找到。读者也可以在名为ns3-dev的源码仓库找到当前的NS-3开发树。还有NS-3的之前发行版本和最新测试版本的代码。
2 下载安装NS3
2.1 下载NS3软件包
我们以Ubuntu Linux环境为例,下载安装NS-3,并通过运行简单的脚本,来验证是否安装成功。
新建目录,用于存放下载的NS3软件包。目录名可自取。如新建名为ns3_install的目录:
mkdir ns3_install
进入ns3_install目录,下载最新版本的NS3源代码到该目录下,并解压缩。以下载ns-3.15版本源代码为例:
cd ns3_install
wget https://www.nsnam.org/release/ns-allinone-3.15.tar.bz2
tar xjfns-allinone-3.15.tar.bz2
解压缩之后的目录名为ns-allinone-3.15/。
2.2 编译NS3软件包
2.2.1 使用build.py编译
第一次编译NS3软件包,建议用./build.py --enable-examples --enable-tests命令,对下载的源代码进行整体编译。
首先切换到上文解压缩后的目录下:
cd ns-allinone-3.15
键入如下的命令,编译脚本开始编译,初次编译用时较长,请耐心等待:
./build.py --enable-examples --enable-tests
编译脚本开始编译下载的ns3时,会看到大量常见的编译器输入信息。最后会看到编译成功的消息:
‘build’ finished successfully (40m30.586s)
编译完成之后,在当前目录(ns-allinone-3.15)下会产生一个名为ns-3.15/的目录,切换到该目录下
cd ns-3.15
2.2.2使用waf编译
除了以上介绍的方法,我们也可以用waf进行NS3源代码的编译。在用waf进行编译之前,先来认识一下waf。
读者下载NS3的源码之后,需要对源码进行编译来生成可执行程序。正如源码管理方式多种多样,编译源码也有多种工具。最常用的工具是make。Make的不足在于,它可能是编译大型和高可配置型系统最难的一种方法。因此,有很多替代工具被开发出来。最近,大型高可配置系统的编译工具大多选择用Python语言来开发。
NS3的编译系统采用了Waf。它是用Python开发的新一代编译管理系统。读者不必掌握python,即可编译现有的NS3项目。如果读者想要扩展现有的NS3系统,大多数情况只需了解Python知识的很少且非常直观的一个子集。
更多关于Waf的细节,可以通过http://code.google.com/p/waf/获取。
然后进入本节的主题,开始使用waf来配置和编译NS3软件包。
使用waf对NS3源代码进行编译时,可以分为优化编译和调试编译两种情况。默认情况将进行调试编译。
为了通知waf进行优化编译,你需要执行如下的命令:
./waf clean
./waf -d optimized --enable-examples --enable-tests configure
为防止重复编译可能带来的错误,第一条命令首先清空以前编译的内容(编译过的内容位于ns-3.15/build/目录下)。该命令不是必须的,但这样做是一个好的习惯。键入第二条命令后,编译系统开始检查各种依存关系,并进行编译。你可以看到类似如下的输出:
Checking for program g++ : ok /usr/bin/g++
Checking for program cpp : ok /usr/bin/cpp
Checking for program ar : ok /usr/bin/ar
Checking for program ranlib : ok /usr/bin/ranlib
Checking for g++ : ok
………………
‘configure’ finished successfully (2.870s)
最后一条语句表明,顺利编译完成。
在编译时需要注意的是,部分ns-3特性默认并不是开启的,需要底层系统的支持才能够顺利运行。例如:为了运行XmlTo, 系统必须安装libxml-2.0库,如果没有发现这个库,相应的ns-3特性就不会被激活,这会以消息显示出来。另一个需要注意的地方是:对于一些特定的程序,需要使用sudo命令来设置该程序,而这些程序默认可能设置为关闭的,所以此性质会显示出未激活。
同样,我们也可以进行调试编译(为后续说明的方便,建议编译时选择调试编译模式):
./waf clean
./waf -d debug --enable-examples --enable-tests configure
此外可以通过参数-o,更改编译的目标目录,默认编译目录为build/。可以通过如下命令,更改编译的目标目录为build/debug/
./waf -d debug -o build/debug --enable-examples --enable-tests configure
在waf中还有许多其它的配置和编译选项可用。可以通过以下命令察看更多的选项:
./waf--help
2.3 对编译的正确性进行测试
在编译完成之后,可以通过运行“./test.py -c core”脚本进行ns-3软件包的正确性测试,
./test.py -c core
这些测试可以被waf并行执行,最后可以看到如下的结果:
136 of 139 tests passed (136 passed, 3 skipped, 0 failed, 0 crashed, 0 valgrind errors)
在测试的过程中,可以看到类似于如下的信息:
Waf: Entering directory ‘/home/……/ns-allinone-3.15/ns-3.15/build’
Waf: Leaving directory ‘/home/……/ns-allinone-3.15/ns-3.15/build’
‘build’ finished successfully (1.799s)
PASS: TestSuite ns3-wifi-interference
PASS: TestSuite ipv4-address-helper
PASS: TestSuite devices-wifi
PASS: TestSuite propagation-loss-model
...
136 of 139 tests passed (136 passed, 3 skipped, 0 failed, 0 crashed, 0 valgrind errors)
用户通常可以运行此命令来检查NS3软件包是否正确编译了。
2.4 运行第一个脚本
我们通常使用waf运行脚本程序。在正确编译了NS3软件包之后,如果要运行一个程序,只需在waf后加入--run选项即可。让我们在ns-3环境下运行常见的Hello Simulator程序,该程序就相当于学习一门语言时的Hello World程序:
./waf --run hello-simulator
Waf会首先检查程序正确编译了,而且还可以根据需要重新执行编译。Waf执行了此程序,并输出如下信息:
Hello Simulator
如果没有看到输出“Hello Simulator”,说明你是在优化编译模式下进行的编译,在优化编译模式下,默认将关闭控制台输出。可以通过以下命令:
./waf clean
./waf -d debug --enable-examples --enable-tests configure
在调试编译模式下进行重新编译。
到现在为止,我们顺利的下载编译了NS3软件包,并通过测试和运行Hello Simulator脚本程序来验证了软件包正确性。如果想在其他工具下如gdb或者valgrind下运行程序,可参见Wiki条目。
3 NS3快速入门
本章节通过阅读分析一个例子程序(first.cc)的源代码,并通过运行该例子程序,快速理解ns3中的几个概念。
3.1 NS3中的几个关键概念
3.1.1 节点Node
在网络术语中,任何一台连接到网络的计算设备被称为主机,亦称为终端。NS3是一个网络模拟器,而非一个专门的因特网模拟器,为此我们避开术语“主机”,因为这个词太容易让人联想到因特网和及其相关协议。因此,我们选用了一个来源于图论,在其他网络模拟器中亦广泛使用的术语:节点。
NS3中基本计算设备被抽象为节点。节点由用C++编写的Node类来描述。Node类提供了用于管理计算设备的各种方法。
可以将节点设想为一台可以添加各种功能的计算机。为了使一台计算机有效地工作,我们可以给它添加应用程序,协议栈,外设卡及驱动程序等。NS3采用了与此相同的模型。
3.1.2 信道
在现实世界中,人们可以把计算机连接到网络上。通常我们把网络中数据流流过的媒介称为信道。当你把以太网线插入到墙壁上的插孔时,你正通过信道将计算机与以太网连接。在NS3中,可以把节点连接到代表数据交换信道的对象上。在这里,基本的通信子网这一抽象概念被称为信道,用C++编写的Channel类来描述。
Channel类提供了管理通信子网对象和把节点连接至信道的各种方法。信道类同样可以由开发者以面向对象的方法自定义。一个信道实例可以模拟一条简单的线缆(wire),也可以模拟一个复杂的巨型以太网交换机,甚至无线网络中充满障碍物的三维空间。
在本章中我们将使用几个信道模型的实例,包括:CsmaChannel, PointToPointChannel和WifiChannel。举例来说,CsmaChannel信道模拟了用于一个可以实现载波侦听多路访问的信道,这个信道具有和以太网相似的功能。
3.1.3 网络设备
如果想把一台计算机连接到网络上,必须在计算机上安装有网卡。一张网卡如果缺少控制硬件的软件驱动是不能工作的。在Unix/Linux系统中,外围硬件被划为“设备”。设备通过驱动程序来控制,而网卡通过网卡驱动程序来控制。在Unix/Linux系统中,网卡被称为像eth0这样的名字。
在NS3中,网络设备这一抽象概念相当于硬件设备和软件驱动的总和。NS3仿真环境中,网络设备相当于安装在节点上,使得节点通过信道和其他节点通信。像真实的计算机一样,一个节点可以通过多个网络设备同时连接到多条信道上。
网络设备由用C++编写的NetDevice类来描述。NetDevice类提供了管理连接其他节点和信道对象的各种方法,并且允许开发者以面向对象的方法来自定义。我们在本教程中将使用几个特定的网络设备的实例,它们分别是CsmaNetDevice, PointToPointNetDevice, 和 WifiNetDevice。正如以太网卡被设计成在以太网中工作一样,CsmaNetDevice被设计成在csma信道中工作,而PointToPointNetDevice 在PointToPoint信道中工作,WifiNetNevice在wifi信道中工作。
3.1.4 应用程序
计算机软件通常可分为两大类:系统软件和应用软件。系统软件根据计算模型配置,并管理计算机中的各种资源,如内存,处理器周期,硬盘,网络等。系统软件通常并不直接使用这些资源来完成用户任务。用户往往需要运行应用程序来完成一些特定的任务,而应用程序需要使用由系统软件控制的资源。
通常,系统软件和应用软件的界线表现为特权级别的变化,而这种变化是通过操作系统的自陷功能(operating system traps)来实现的。在NS3中并没有真正的操作系统的概念,更没有特权级别或者系统调用的概念。然而,我们有应用程序的概念。正如“现实世界”中在计算机上运行应用程序以执行各种任务一样,NS3仿真环境中的应用程序在节点上运行来驱动模拟过程。
在NS3中,需要被仿真的用户程序被抽象为应用。用Application类来描述。这个类提供了管理仿真过程中用户层应用的各种方法。开发者应当用面向对象的方法自定义和创建新的应用。在本教程中,我们会使用Application类的两个实例:UdpEchoClientApplication和UdpEchoServerApplication。这些应用程序包含了一个client应用和一个server应用来发送和回应仿真网络中的数据包。
3.2 分析例子程序first.cc的源代码
进入ns-3.15/examples/tutorial目录。你会发现一个叫first.cc的文件。这一个脚本会在两个节点间创建一个简单的点到点的连接,并且在这两个节点之间传送一个数据包。为方便后续分许,先将first.cc的源代码粘贴如下:
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/internet-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/applications-module.h"
using namespace ns3;
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
intmain (int argc, char *argv[])
{
LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);
NodeContainer nodes;
nodes.Create (2);
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
InternetStackHelper stack;
stack.Install (nodes);
Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = address.Assign (devices);
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));
Simulator::Run ();
Simulator::Destroy ();
return 0;
}
3.2.1 模块包含
代码一般是以一系列的include声明开始的:
#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
为了帮助高层的脚本用户处理大量的系统中的include文件,我们把所有的包含文件,根据模块功能,进行了大致的分类。我们提供了一个单独的include文件,这个文件会递归加载所有会在每个模块中会被使用的include文件。NS3提供了按大致功能分类的一组include文件,在使用时只需选择包含这几个包含文件(include文件),而不用考虑复杂的依赖关系,省去在寻找所需要的头文件上花费的不必要的时间。这不是最有效地方法但很明显让编写脚本文件容易多了。
在编译的过程中,每一个ns-3的include文件被放在build目录下一个叫ns3的目录中,这样做可以避免include文件名的冲突。ns3/core-module.h与src/core目录下的模块相对应。查看ns3目录会发现大量的头文件。当你编译时,Waf会根据配置把在ns3目录下的公共的头文件放到build/debug 或者build/optimized目录下。Waf也会自动产生一个模块include文件来加载所有的公共头文件。
当然,如果遵循着这个手册走的话,你可能已经使用过如下命令:
./waf -d debug --enable-examples --enable-tests configure
来配置工程以完成调试工作。你可能同样使用了如下命令:
./waf
来编译ns-3。现在如果你进入../../build/debug/ns3 目录的话你会发现本节开头提到的四个头文件。仔细看一下这些文件的内容,会发现它们包含了相关模块中的所有的include文件。
3.2.2 命名空间
在first.cc脚本的下一行是namespace的声明。
using namespace ns3;
NS3工程是在一个叫做ns3的C++ 命名空间中实现的。这把所有与ns3相关的声明,集中在一个与全局命名空间相区别的命名空间中。我们希望这样会给ns3与其他代码的集成带来好处。C++用“using”语句用来把ns-3 namespace引入到当前的(全局的)作用域中。这个声明就是说,你不用为了使用ns-3的代码而必须在所有的ns-3代码前打上ns3:: 作用域操作符。如果对命名空间并不熟悉,可以查阅任何的C++手册并比较ns3命名空间和标准”std”命名空间的使用。
3.2.3 日志
下一句脚本如下:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
这一行声明了一个叫FirstScriptExample的日志组件,通过引用FirstScriptExample这个名字的操作,可以实现打开或者关闭控制台日志的输出。
3.2.4 Main函数
下面的脚本是:
int
main (int argc, char *argv[])
{
这就是你的脚本程序的主函数的声明。正如任何其它C++程序一样,你需要定义一个会被第一个执行的主函数。你的ns-3脚本没有什么特别的,就和一个普通的C++程序一样。
再接下来两行脚本是用来使两个日志组件生效的。它们被内建在Echo Client和Echo Server应用中:
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
3.2.5 使用基本对象模型搭建仿真拓扑
3.2.5.1使用NodeContainer类
在first.cc脚本中的下面两行将会创建ns-3节点对象。
NodeContainer nodes;
nodes.Create (2);
上面的第一行只是声明了一个名为”nodes”的NodeContainer。第二行调用了nodes对象的Create()方法创建了两个节点。
3.2.5.2使用PointToPointHelper类
接下来,我们将会用到被称为拓扑辅助工具的helper类。这些helper类里面封装了低级的方法,有助于我们高效的建立仿真拓扑。
回忆一下我们的两个关键抽象概念:网络设备、信道。在真实的世界中,这些东西大致相当于网卡和网线。需要说明的是这两样东西紧密的联系在一起而不能够把它们交互地使用(比如以太网设备和无线信道就不能一起使用)。在这个脚本中使用了PointToPointHelper来配置和连接网络设备PointToPointNetDevice和信道PointToPointChannel对象。
在脚本中下面的三句话是:
PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
其中第一行,
PointToPointHelper pointToPoint;
在栈中初始化了一个PointToPointHelper的对象pointToPoint。而紧接着的下一行,
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
从上层的角度告诉PointToPointHelper对象当创建一个PointToPointNetDevice对象时使用“5Mbps"来作为数据速率。
从细节方面讲,字符串“DataRate”与PointToPointNetDevice的一个属性相对应。如果查看ns3::PointToPointNetDevice 类,并阅读GetTypeId 方法的文档,你会发现设备定义了一系列属性,在这些属性中就有“DataRate”。大部分用户可见的ns-3对象都有类似的属性列表。正如你在下面的部分会看到的一样,我们使用了这个机制以方便地配置仿真器,而不用重新对源代码进行编译。
与PointToPointNetDevice上的“DataRate”类似,PointToPointChannel也有一个Delay属性:
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
告诉PointToPointHelper使用"2ms"(2毫秒)作为每一个被创建的点到点信道传输延时值。
3.2.5.3使用NetDeviceContainer类
现在我们有一个包含两个节点的NodeContainer对象。我们有一个准备在两个节点之间创建PointToPointNetDevices和wirePointToPointChannel对象的PointToPointHelper对象。正如我们使用NodeContainer对象来为我们创建节点,我们会让pointToPointHelper来做关于创建,配置和安装设备的工作。
我们使用一个NetDeviceContainer对象来存放需要所有被创建的NetDevice对象,就像我们使用一个NodeContainer对象来存放我们所创建节点。下面两行代码:
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
会完成设备和信道的配置。第一行声明了上面提到的设备容器,第二行完成了主要工作。PointToPointHelper的Install()方法以一个NodeContainer对象作为一个参数。在Install()方法内,一个NetDeviceContainer被创建了。对于在NodeContainer 对象中的每一个节点(对于一个点到点链路必须明确有两个节点),都将有一个PointToPointNetDevice被创建和保存在设备容器内,有一个PointToPointChannel对象被创建,两个PointToPointNetDevices与之连接。当PointToPointHelper对象创建时,那些在helper中被预先设置的属性被用来初始化对象对应的属性值。
当调用了pointToPoint.Install(nodes)后,我们会有两个节点,每一个节点安装了点到点网络设备,在它们之间是一个点到点信道。两个设备会被配置在一个有2ms传输延时的信道上以5Mbps的速率传输数据。
3.2.5.4使用InternetStackHelper类
我们现在已经配置了节点和设备,但是我们还没有在节点上安装任何协议栈。下面两行代码完成这个任务:
InternetStackHelper stack;
stack.Install (nodes);
类InternetStackHelper 是一个辅助安装网络协议栈的helper类。其中Install()方法以NodeContainer 对象作为参数,当它被执行后,它会为节点容器中的每一个节点安装一个网络协议栈(TCP,UDP,IP等)。
3.2.5.5使用Ipv4AddressHelper类
下面我们需要为节点上的设备设置IP地址。我们也提供了一个helper类来管理IP地址的分配。当执行实际的地址分配时唯一用户可见的API是设置IP地址和子网掩码。
在我们的范例脚本文件first.cc的下两行代码
Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");
声明了一个helper对象,并且告诉它应该开始从10.1.1.0开始以子网掩码为255.255.255.0分配地址。地址分配默认是从1开始并单调的增长,所以在这个基础上第一个分配的地址会是10.1.1.1,紧跟着是10.1.1.2等等。底层NS3系统事实上会记住所有分配的IP地址,如果你无意导致了相同IP地址的产生,这将是一个致命的错误(顺便说一下,这是个很难调试正确的错误)。
下面一行代码,
Ipv4InterfaceContainer interfaces = address.Assign (devices);
完成了真正的地址配置。在NS3中我们使用 Ipv4Interface对象将一个IP地址同一个网络设备关联起来。正如我们有时候需要一个网络设备列表一样,我们有时候需要一个 Ipv4Interface对象的列表。Ipv4InterfaceContainer提供了这样的功能。
现在我们有了一个安装了协议栈,配置了IP地址的点到点的网络。剩下的所要做的事情是运用它来产生数据通信。
3.2.5.6 Applications类
NS3系统的另一个核心抽象是Application类。在这个脚本中我们用两个特定的Application类:UdpEchoServerApplication和UdpEchoClientApplication。正如我们先前声明过的一样,我们使用helper对象来帮助配置和管理潜在的对象。在这里,我们用UdpEchoServerHelper 和UdpEchoClientHelper对象来使我们的工作更加容易点。
首先来看UdpEchoServerHelper类:
下面的代码用来在我们之前创建的节点上设置一个UDP 回显服务应用。
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
上面一片代码中的第一行声明了UdpEchoServerHelper。像往常一样,这个并非应用本身,这是一个用来帮助创建真正应用的helper对象。我们约定在helper类的对象中放置必需的属性。本例中,除非我们告知helper对象服务器和客户端所共知的一个端口号,否则这个helper对象是不会起任何作用的。
同其它helper对象类似,UdpEchoServerHelper对象有一个Install()方法。实际上是这个方法的执行,才初始化回显服务器的应用,并将应用连接到一个节点上去。有趣的是,install()方法把NodeContainter参数,正如我们看到的其他安装方法一样。这里有一个C++隐式转换,此转换以nodes.Get(1)的结果作为输入,并把它作为一个NodeContainer的构造函数的参数,最终这个新构造的NodeContainer被送入Install方法中去。
我们现在会看到echoServer.Install将会在管理节点的NodeContainer容器索引号为1的机节点上安装一个UdpEchoServerApplication。安装会返回一个容器,这个容器中包含了指向所有被helper对象创建的应用指针。
应用程序对象需要一个时间参数来“开始”产生数据通信并且可能在一个可选的时间点“停止”。我们提供了开始和停止的两个参数。这些时间点是用ApplicationContainer的方法Start和Stop来设置的。这些方法以”Time”对象为参数。本例中,我们传递了double类型对象1.0到Seconds的一个方法,通过seconds()方法,把它转换到ns-3的Time对象。需要注意的是,这里的转换规则是模型的作者所控制的,并且C++也有它自己的标准,所以你不能总是假定参数会按照你的意愿顺利地转换。下面两行,
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
会使echo服务应用在1s时开始(生效)并在10s时停止(失效)。既然我们已经声明了一个模拟事件(就是应用的停止事件)在10s时被执行,模拟至少会持续10s。
然后再来看UdpEchoClientHelper类:
echo客户端应用的设置与回显服务器端类似。也有一个UdpEchoClientHelper来管理UdpEchoClientApplication。
UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));
ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));
然而,对于echo客户端,我们需要设置五个不同的属性。首先两个属性是在UdpEchoClientHelper的构建过程中被设置的。按照UdpEchoClientHelper构造函数的格式,我们传递了”RemoteAdress”和”RemotePort”属性,来实例化对象。
在上面的第一行代码中,我们创建了一个UdpEchoClientHelper的对象,并告诉它设置客户端的远端地址为服务器节点的IP地址。我们同样告诉它准备发送数据包到端口9。
“MaxPackets”属性告诉客户端我们所允许它在模拟期间所能发送的最大数据包个数。
“Interval”属性告诉客户端在两个数据包之间要等待多长时间。
“PacketSize”属性告诉客户端它的数据包应该承载多少数据。本例中,我们让客户端发送一个1024字节的数据包。
正如echo服务端一样,我们告诉echo客户端何时来开始和停止,这里我们使客户端在模拟器中时间为2s的时候开始(即服务端生效1s后才开始)。
3.2.5.7Simulator类
下面我们所需要做的就是运行模拟器,这是用全局函数Simulator::Run.来做到的:
Simulator::Run ();
当我们调用了如下方法时:
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
...
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));
实际上我们是在模拟器中1.0s,2.0s,和10.0s时预设了时间的发生。当Simulator::Run被调用时,系统会开始遍历预设事件的列表并执行。首先它会在1.0s时运行事件,这个事件会使echo服务端应用生效(这个事件会预设更多的其他事件)。接下来仿真器会运行在t=2.0s时的事件,即让echo客户端应用开始。同样的,这个事件可能会预定更多的其他事件。在echo客户端应用中的开始事件的执行会通过给服务端传送一个数据包来开始仿真的数据传送阶段。
发送一个数据包给服务端会引发一系列更多的事件。这些事件会被预设在此事件之后,并根据我们已经在脚本中设定的时间参数来执行数据包的应答。
其实,我们只发送了一个数据包(回忆一MaxPackets属性被设置为1),在此之后,那个被单独的客户端应答请求所引发的连锁反应会停止,并且模拟器会进入空闲状态。当这发生时,生下来的事件就是服务端和客户端的Stop事件。当这些事件被执行后,就没有将来的事件来执行了,函数Simulator::Run会返回。整个模拟过程就结束了。
下面剩下的事情就是清理了。这个通过调用全局函数Simulator::Destroy来完成。当该方法被执行后,模拟器中所有创建的对象将被销毁。你自己并不需要追踪任何对象,你所需要做的仅仅是调用Simulator::Destroy并且退出。ns-3系统会帮你料理这些繁杂的任务。在first.cc脚本中对应的代码如下:
Simulator::Destroy ();
return 0;
}
3.3 运行first.cc脚本程序
要运行自己的脚本,你所需要做的仅仅是把你的脚本放到scratch目录下,并运行waf,这样你的脚本就会被编译。
接下来我们来运行上面分析过的first.cc脚本。我们先回到高层目录后复制examples/tutorial/first.cc文件到scratch目录下,并改名为myfirst.cc文件。
cp examples/tutorial/first.cc scratch/myfirst.cc
现在使用waf命令来编译自己的第一个实例脚本:
./waf
你应该可以看到消息报告说你的myfirst范例被成功编译了。
Waf: Entering directory `/home/…/build'
………
Waf: Leaving directory `/home/…/build'
'build' finished successfully (2.357s)
现在你能够运行这个例子(注意如果你在scratch目录编译了你的程序,你必须在scratch目录外运行它):
./waf --run scratch/myfirst
注意这里是myfirst而不是myfirst.cc!
你应该能看到一些输出:
Waf: Entering directory `/home/…/build'
Waf: Leaving directory `/home/…/build'
'build' finished successfully (0.418s)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2
这里可以看到编译系统先检查文件被编译了,接着运行了它。你看到在echo日志构件显示了它已经发送了1024字节到在10.1.1.2的echo服务端。还可以看到回显服务器端的日志构件显示他从10.1.1.1接收到了1024字节。接下来echo服务端应答了数据包,你能看到echo客户端记录了它已经接收到了从服务端发送过来的回显数据包。
转自:http://wuleiblog.cn/articles/64.html