ns-3储备知识

ns-3简介

n s − 3   ns{-}3\, ns3模拟器 ( n s − 3   s i m u l a t o r ) (\mathbf{ns}{-}\mathbf{3\,simulator}) (ns3simulator)是一个离散事件模拟器 ( A   D i s c r e t e − E v e n t   N e t w o r k   S i m u l a t o r \mathbf{A\,Discrete}{-}\mathbf{Event \,Network \,Simulator} ADiscreteEventNetworkSimulator),是一个可扩展的网络模拟器平台,旨在满足网络方面的学术研究和教学需求.

简而言之, n s − 3   ns{-}3\, ns3提供了很多分组数据网络如何运作的模型,还提供一个仿真引擎让使用者进行仿真实验. n s − 3   ns{-}3\, ns3适用于研究困难或者不可能在现实系统中进行的课题,使用者可以在高度可控、可重现的环境中进行实验, 学习网络是如何工作的.

n s − 3   ns{-}3\, ns3使用的模型专注于模拟互联网协议和网络的运行,但   n s − 3   \,ns{-}3\, ns3也不限于模拟网络系统.

  • n s − 3   ns{-}3\, ns3需要使用者使用命令行、C++和(或)Python软件开发工具
  • n s − 3   ns{-}3\, ns3主要在   L i n u x   \,Linux\, Linux或者   m a c O S   \,macOS\, macOS系统中使用,   W i n d o w s   \,Windows\, Windows系统需要使用   L i n u x   \,Linux\, Linux虚拟机.

使用Waf构建ns-3

Waf简介

当你把   n s − 3   \,ns{-}3\, ns3的源代码下载到了你的系统,你需要编译这些代码来生成可用程序. n s − 3   ns{-}3\, ns3使用 Waf 做为编译系统 ( b u i l d   s y s t e m ) (\mathbf{build\,system}) (buildsystem),Waf是新一代基于Python的编译系统之一. 用户不需要知道任何关于Python的知识,就可以使用Waf完成   n s − 3   \,ns{-}3\, ns3系统的构建.

更多关于Waf的细节:

构建过程

$ ./waf clean
$ ./waf configure --build-profile=optimized --enable-examples --enable-tests

ns-3关键抽象

  n s − 3   \,ns{-}3\, ns3模拟过程中自然需要对网络构件进行适当抽象,
下面介绍   n s − 3   \,ns{-}3\, ns3中几个关键的抽象:

Key Abstractions
Node
NetDevice
Channel
Application
Topology Helper

Node - 结点

在网络术语中,一台连接到网络的计算设备被称作一台主机 ( h o s t ) (\mathbf{host}) (host)或终端 ( e n d   s y s t e m ) (\mathbf{end\,system}) (endsystem). 由于   n s − 3   \,ns{-}3\, ns3是一个网络模拟器,而非专用于因特网的模拟器, 所以在   n s − 3   \,ns{-}3\, ns3不使用主机这一术语,而故意使用了在其他模拟器中也通用的Node(结点). 结点这个词源于图论.

也就是说,   n s − 3   \,ns{-}3\, ns3中所有终端都被抽象为结点.   n s − 3   \,ns{-}3\, ns3中可以将一个结点视为一台可添加各项功能的计算机. 可以添加应用、协议栈、外设卡及其相关驱动程序等让计算机有效工作.

结点由使用C++编写的Node类来表示. 这个类提供了用于管理终端的各种方法.

Application - 应用

计算机软件可被分为两大类: 系统软件 ( S y s t e m   S o f t w a r e ) (\mathbf{System\,Software}) (SystemSoftware)和应用软件 ( A p p l i c a t i o n ) (\mathbf{Application}) (Application). 系统软件根据一些计算模型管理计算机中的各种资源,比如内存、处理器周期、硬盘、网络等. 系统软件通常不会直接使用这些资源来完成使用户直接受益的任务. 用户往往需要运行一个应用程序来完成一些特定的任务,而应用程序需要使用由系统软件控制的资源.

通常,系统软件和应用软件的界线表现为特权级别 ( p r i v i l e g e   l e v e l ) (\mathbf{privilege\,level}) (privilegelevel)的变化,这种变化是通过操作系统的自陷功能来实现的. 在   n s − 3   \,ns{-}3\, ns3中没有真正的操作系统的概念,也更没有特权级别或系统调用的概念.

然而,   n s − 3   \,ns{-}3\, ns3中有Application(应用)的概念. 正如现实世界中在计算机上运行执行各种任务的应用程序一样,   n s − 3   \,ns{-}3\, ns3世界中的应用程序运行在结点上,驱动模拟过程。

  n s − 3   \,ns{-}3\, ns3中,一些需要模拟的产生活动的用户程序被抽象为应用. 应用程序由使用C++编写的Application类来表示. 这个类提供了用于管理模拟过程中用户级应用的各种方法. 开发者需使用面向对象的方法自定义和创建新的应用.

  n s − 3   \,ns{-}3\, ns3的Tutorial的实例中,会使用到Application类的两个实例:UdpEchoClientApplication和UdpEchoServerApplication. 这两个应用会包含了一个客户机/服务器的应用集合,用于生成和回应仿真网络中的数据包.

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对象一样,UdpEchoServerHelper对象也有一个Install方法. 通过这个方法,才实际完成了回显服务应用的初始化,并将该应用安装到某个结点上. 值得注意的是,Install方法中的参数是 nodes.Get (1),Get (1) 返回的是一个指向NodeContainer对象中索引为1的结点的智能指针对象——Ptr<Node>. 通过Install方法,就在这个索引号为1的结点上安装了一个回显服务应用.

第三、四行,应用需要一个时刻来“开始”产生数据通信,并且可能还要在一个可选的时刻“停止”. ApplicationContainer提供了设置开始和停止时刻的方法. 这两个方法都以Time对象做为参数. 三四行中通过调用Seconds()方法,把double类型的数值转换为 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3的Time对象. 回显服务应用将在1s时生效,并在10s时失效. 由于设定了停止时间是10s,因而模拟过程将至少持续10s.

UdpEchoClientHelper

UDP回显客户端应用的设置与回显服务端的设置非常类似,也有一个UdpEchoClientHelper用于创建对应应用. 然而对于每一个回显客户端,都需要设置五个不同的属性.

其中两个属性是在构建UdpEchoClientHelper对象中设置的. 必须给UdpEchoClientHelper类的构造器提供“RemoteAdress(远端地址)”和“RemotePort(远端端口)”两个属性,才能实例化helper对象.

UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.)));
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

在上面代码的第一行中,首先创建了一个UdpEchoClientHelper对象echoClient,将客户端的远端地址设置为服务器中索引为1结点的IP地址,并告诉其准备发送数据包的服务器端口为9.

紧接着的三行代码,通过UdpEchoClientHelper的SetAttribute方法设置了另外三个属性.
“MaxPackets”属性规定在模拟过程中,客户端允许发送数据包的最大数量.
“Interval”属性规定客户端发送两个数据包之间需要等待多长时间.
“PacketSize”属性规定客户端的数据包承载了多少数据.

本例中,设置客户端在整个模拟过程中只能发送一个数据包. 如果要发送下一个数据包,需要等待1s的间隔(本例中无意义). 客户端每次发送的数据包大小为1024个字节.

ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

上面代码的第一行通过向Install方法中传入nodes.Get (0),在索引为0的结点上安装了UDP回显客户端应用. 和回显服务端应用一样,我们也需要告诉回显客户端应用应该何时开始和停止. 设置方法与服务端一致. 本例中,客户端在模拟器中在2s的时候开始生效(即服务端生效1s后才开始).

Channel - 信道

在现实世界中,人们可以把计算机连接到网络. 通常我们把网络中数据流流过的媒介称为信道. 当把以太网线插入到墙壁上的插孔时,就连接到了一个以太网通信信道. 在 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3中,一个通信信道连接了一个结点和一个对象. 基本的通信子网被抽象为为Channel(信道).

信道由使用C++编写的Channel类来表示. Channel类提供了管理通信子网对象和把结点连接至信道的各种方法. Channel类同样可由开发者使用面向对象的方法自定义.

一个信道的实例可以模拟和一条线缆 ( w i r e ) (\mathbf{wire}) (wire)一样简单的事物,也可以模拟如巨型以太网交换机一样复杂的事物,甚至可以模拟无线网络中充满障碍物的三维空间.

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3的Tutorial的实例中,会使用到Channel类的几个信道模型实例:CsmaChannel、PointToPointChannel和WifiChannel. 其中,CsmaChannel模拟了用于一个可以实现载波侦听多路访问的信道,这个信道具有和以太网相似的功能.

Net Device - 网络设备

过去如果想让一台计算机连接到网络上,就必须购买适配的的网络电缆 ( n e t w o r k &ThinSpace; c a b l e ) (\mathbf{network\,cable}) (networkcable)和一个需要被安装进你的计算机中的硬件部件——外设卡 ( p e r i p h e r a l &ThinSpace; c a r d ) (\mathbf{peripheral\,card}) (peripheralcard). 如果外设卡实现了一些网络功能,就被称作网卡 ( N e t w o r k &ThinSpace; I n t e r f a c e &ThinSpace; C a r d s , N I C s ) (\mathbf{Network\,Interface \,Cards, NICs}) (NetworkInterfaceCards,NICs). 现在大多数的电脑都将网络接口的硬件内置,用户看不到. 在Unix/Linux系统中,网卡被称为像eth0这样的名字.

网卡如果缺少控制硬件的软件驱动就不能工作. 在Unix/Linux系统中,外围硬件被称为设备 ( d e v i c e ) (\mathbf{device}) (device). 设备由设备驱动 ( d e v i c e &ThinSpace; d r i v e r ) (\mathbf{device\,driver}) (devicedriver)来控制. 网卡,即网络设备 ( n e t &ThinSpace; d e v i c e s ) (\mathbf{net\,devices}) (netdevices),由网络设备驱动控制. 在Unix/Linux系统中,网络设备通过如eth0这样的名字来指代.

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3中,Net Device(网络设备)这一抽象概念涵盖了硬件设备和软件驱动. 一个网络设备被安装到一个结点,以便使结点能够通过信道与其他结点通信. 和真实的计算机一样,一个结点就可以通过多个网络设备连接到多条信道上.

网络设备由使用C++编写的NetDevice类来表示. NetDevice类提供了管理连接结点对象和信道对象各种方法. NetDevice类同样可由开发者使用面向对象的方法进行自定义.

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3的Tutorial的实例中,会使用到NetDevice类的几个信道模型实例:CsmaNetDevice、PointToPointNetDevice和WifiNetDevice. 正如以太网卡被设计来用于以太网一样,CsmaNetDevice用于CsmaChannel,PointToPointNetDevice用于PointToPointChannel,WifiNetNevice用于WifiChannel.

Topology Helpers

在真实的网络世界中,你会发现一个主机附加(或者内置)了网卡. 在 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3的世界里,你会发现结点和其附属的网络设备. 在大型的仿真网络中,你需要设置很多在结点、网络设备和信道之间的连接.

由于连接网络设备到结点、连接网络设备到信道、分配IP地址等在 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3中是非常普遍的任务, &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3提供了一系列使这些操作变得简单的Topology Helpers . 举个例子,创建一个NetDevice、添加MAC地址、将NetDevice安装到结点、安装协议栈、然后连接到一个信道,这个过程会花费很多不同的 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3核心操作. 如果还要连接多个设备到多点信道,将单个局域网连接到整个网络,更是要求更多的操作.

鉴于此, &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3提供了Topology Helper对象,用于将那些大量不同的操作合并成一个可以简单使用的模型.

NodeContainer

NodeContainer topology helper提供了一个非常方便的方式来创建、管理和访问结点对象.

下面两行代码将创建两个代表计算机的 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3结点对象:

NodeContainer nodes;
nodes.Create (2);

PointToPointHelper

点到点连接是最简单的一种网络连接. 我们使用PointToPointHelper topology helper来完成点到点连接需要的底层工作. 回忆前面提到抽象中的NetDeviceChannel,在现实世界中,他们分别大致对应了网卡和网络线缆,二者是紧密联系在一起的,比如以太网设备和无线信道.

Topology helper保证了这种紧密的关系,因而你会发现下面的代码只使用了一个PointToPointHelper对象就完成了 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3PointToPointNetDevice对象和PointToPointChannel对象的配置和连接.

PointToPointHelper pointToPoint;
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

第一行,声明了一个PointToPointHelper对象pointToPoint;

第二行,告诉pointToPoint当创建PointToPointNetDevice时,使用5Mbps作为数据传输速率 ( d a t a &ThinSpace; r a t e ) (\mathbf{data\,rate}) (datarate). 从更深层次的角度来看,字符串“DataRate”实际上是PointToPointNetDevice的一个属性.

第三行,与“DataRate” 类似,字符串“Delay”是PointToPointChannel的一个属性. 告诉pointToPoint,使用2ms作为后来创建每条点对点信道的传播延迟 ( p r o p a g a t i o n &ThinSpace; d e l a y ) (\mathbf{propagation\,delay }) (propagationdelay).

NetDeviceContainer

就像我们使用NodeContainer topology helper来创建并保存模拟过程的结点一样,我们使用NetDeviceContainer来保存创建好的一系列网络设备对象:

NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);

第一行,声明了一个NetDeviceContainer对象devices.

第二行,PointToPointHelper类的Install方法将NodeContainer对象作为参数,初始化了devices这个NetDeviceContainer对象. 初始化过程中,对于NodeContainer对象中的每个结点(在一个点对点连接中,正好只有两个结点),都会建立一个PointToPointNetDevice对象并保存到devices中去(一共两个). 还会建立一个PointToPointChanne对象,然后连接建立好的两个PointToPointNetDevice对象. 当这些对象都被PointToPointHelper建立好后,之前设定好的属性会被用于初始化相应对象的属性.

在执行完这两句代码后,我们现在有两个结点,两个结点都被安装了点到点的网络设备,两个结点之间由一个点到点的信道连接. 网络设备都配置成五兆位每秒的传输速率,信道有2毫秒的传输延迟.

InternetStackHelper

在配置好结点和设备以后,我们还没有在任何结点上安装协议栈.
下面的两行代码就完成了这个任务:

InternetStackHelper stack;
stack.Install (nodes);

The InternetStackHelper is a topology helper that is to internet stacks what the PointToPointHelper is to point-to-point net devices

InternetStackHelper和网络栈的关系,就如同PointToPointHelper和点到点网络设备的关系一样. Install方法接受一个NodeContainer对象作为参数,当它被执行以后,将会在NodeContainer对象中的每个结点上都安装一个网络栈(TCP、UDP、IP等等).

Ipv4AddressHelper

我们需要给结点上的设备分配IP地址. &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3提供了一个topology helper专用于管理IP地址的分配. 用户唯一可见的接口就是设置IP基地址 ( b a s e &ThinSpace; I P &ThinSpace; a d d r e s s ) (\mathbf{base\,IP\,address}) (baseIPaddress)和网络掩码 ( n e t w o r k &ThinSpace; m a s k ) (\mathbf{network\,mask}) (networkmask)来实现真正的地址分配(helper底层实现).

Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");

上面两行代码中:
第一行,声明了一个Ipv4AddressHelper对象address.
第二行,告诉address对象需要从网络10.1.1.0开始分配IP地址,并使用255.255.255.0掩码来定义可分配的地址位.

默认地址分配会从1开始,然后单调递增,所以第一个从10.1.1.0这个基地址分配的地址会是10.1.1.1,接着是10.1.1.2,以此类推. &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3底层实际上会记录所有分配的IP地址. 如果意外让一个相同的地址生成两次,将会导致致命的错误(极难debug).

接下来的这行代码真正完成了的地址分配任务:

Ipv4InterfaceContainer interfaces = address.Assign (devices);

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3中,通过使用一个Ipv4Interface对象来关联一个网络设备与IP地址. 正如我们有时需要一个网络设备列表,就需要通过NetDeviceContainer来创建一样,有时我们需要一个Ipv4Interface对象的列表,也需要一个Container来完成这个工作. Ipv4InterfaceContainer就提供了这样的功能.

ns-3日志模块

简介

很多大型系统都支持一些类型的日志记录工具, &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3系统也不例外. 在一些系统中,只有错误信息会被记录到operator console(一种基于Unix内核的系统的典型stderr). 在另一些系统中,警告信息会同更多的细节信息一起输出. 还有的系统中,日志记录工具被用来输出debug信息,而很快使输出信息变得模糊.

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3认为所有详尽程度的信息都是有用的,因而提供了一个可供选择、多级别的方法来记录日志. 日志可以完全被禁用,可以对某个部分组件启用,也可以全局启用. &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3还提供了不同详尽程度的日志级别.

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3的日志模块提供了直观而简单的方式来获取模拟过程中的有用信息. 非常适合用于调试信息、警告信息和错误信息,以及用来简单、快速地获取脚本和模型信息.

以下是在目前系统中,按详尽程度依次递增的七个日志级别:

日志级别(LOG_TYPE)记录信息
LOG_ERROR记录错误信息 (相关宏: NS_LOG_ERROR)
LOG_WARN记录警告信息 (相关宏: NS_LOG_WARN)
LOG_DEBUG记录相对罕见的特殊调试信息 (相关宏: NS_LOG_DEBUG)
LOG_INFO记录关于程序进程的信息 (相关宏: NS_LOG_INFO)
LOG_FUNCTION记录描述每个调用函数的信息 (相关宏: NS_LOG_FUNCTION,针对成员函数;NS_LOG_FUNCTION_NOARGS,针对静态函数)
LOG_LOGIC记录描述一个函数内部的逻辑流程的信息 相关宏: NS_LOG_LOGIC)
LOG_ALL记录以上所有级别的信息 (无相关宏)
  • 使用以上任意一种日志级别(LOG_TYPE),只能启动对应宏提供的信息. 但还可以使用LOG_LEVEL_TYPE,它会把低于该级别的所有日志信息也添加进来. 比如LOG_INFO只会记录程序进程的信息,而通过使用LOG_LEVEL_INFO,就可以同时获得程序进程信息、调试信息、警告信息和错误信息.

此外还提供了一种日志宏NS_LOG_UNCOND,它无条件地记录相关信息,与日志级别或者组件选择无关.

每个级别都可以被单独使用(LOG_TYPE)或累积使用(LOG_LEVEL_TYPE). 日志可以通过使用一个shell环境变量或者调用日志系统函数来配置.

启用日志

下面我们以运行examples/tutorial/fisrt.cc为例了解使用日志的一些细节:

$ ./waf --run scratch/myfirst

得到运行后的输出:

$ Waf: Entering directory `.../build'
Waf: Leaving directory `.../build'
'build' finished successfully (0.413s)
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

输出中的Sent和Receive信息实际上是来自UdpEchoClientApplication和UdpEchoServerApplication的日志信息.

下面我们以客户端应用UdpEchoClientApplication为例,

现在客户端应用将对fisrt.cc中下面的代码做出回应:

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);

这行代码将启用LOG_LEVEL_INFO等级的日志. 如前面介绍一样,因为使用了带LEVEL标记的等级,实际上我们将启用LOG_INFO和更低等级的所有日志(即启用了NS_LOG_INFO, NS_LOG_DEBUG, NS_LOG_WARN和NS_LOG_ERROR).

我们还可以通过环境变量NS_LOG来提升日志级别,而不需要改变代码和重新编译,像这样:

$ export NS_LOG=UdpEchoClientApplication=level_all

这行代码将环境变了NS_LOG设置为一个字符串.

在赋值语句UdpEchoClientApplication=level_all中,左侧UdpEchoClientApplication是我们想要设置日志组件的名称. 右侧是我们想要使用的记号(将启用该应用所有调试等级的日志). 如果这样设置完NS_LOG变量以后再运行代码, &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3日志系统将发现这一改变,然后你会得到下面的输出:

Waf: Entering directory `.../build'
Waf: Leaving directory `.../build'
'build' finished successfully (0.404s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()

其中增加了来自NS_LOG_FUNCTION级别的调试信息,它将展示在代码执行过程中,该应用中每次被调用的函数. &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3系统并没有规定模型必须支持任何特定类型的日志功能,具体需要多少的信息由开发者决定.

  • 值得注意的是在上面函数调用的调试信息中,字符串UdpEchoClientApplication和函数名之间有一个冒号( : : :). 如果你熟悉C++类可能会感到诧异,这里按理应该使用双冒号的域作用解析运算符( : : :: ::). 实际上这是故意的,因为UdpEchoClientApplication字并不是方法对应类的名字,而是日志记录组件的名字. 使用单个冒号这种微妙的方式即是为了标识二者的差异.

在有些情况下,会很难分辨某条日志信息到底是哪个方法生成的. 比如你可能想知道上面输出中Sent 1024 bytes to 10.1.1.2这条信息是来自哪里的. 你可以通过使用下面的语句来解决这个问题.

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func'

执行完该语句后再运行代码,你会发现上面语句中给出的日志组件发出的信息都会被加上组件名作为前缀:

Waf: Entering directory `.../build'
Waf: Leaving directory `.../build'
'build' finished successfully (0.417s)
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
UdpEchoClientApplication:HandleRead(0x6241e0, 0x624a20)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()

现在就能轻松找出哪些信息是来自回显客户端应用的. 反之,没有UdpEchoClientApplication前缀的Received 1024 bytes from 10.1.1.1显然就应该是来自回显服务端应用的信息.

我们还可以通过冒号分隔的列表,同时启用多个组件对应日志信息和前缀:

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:UdpEchoServerApplication=level_all|prefix_func'

再次运行代码就可以得到下面非常详细的输出:

Waf: Entering directory `.../build'
Waf: Leaving directory `.../build'
'build' finished successfully (0.406s)
UdpEchoServerApplication:UdpEchoServer()
UdpEchoClientApplication:UdpEchoClient()
UdpEchoClientApplication:SetDataSize(1024)
UdpEchoServerApplication:StartApplication()
UdpEchoClientApplication:StartApplication()
UdpEchoClientApplication:ScheduleTransmit()
UdpEchoClientApplication:Send()
UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
UdpEchoServerApplication:HandleRead(): Echoing packet
UdpEchoClientApplication:HandleRead(0x624920, 0x625160)
UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
UdpEchoServerApplication:StopApplication()
UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

有时候可能需要知道某个组件生成信息的时刻,可以进行如下设置:

$ export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time:UdpEchoServerApplication=level_all|prefix_func|prefix_time'

再次运行将会得到如下含时刻信息的输出:

Waf: Entering directory `.../build'
Waf: Leaving directory `.../build'
'build' finished successfully (0.418s)
0s UdpEchoServerApplication:UdpEchoServer()
0s UdpEchoClientApplication:UdpEchoClient()
0s UdpEchoClientApplication:SetDataSize(1024)
1s UdpEchoServerApplication:StartApplication()
2s UdpEchoClientApplication:StartApplication()
2s UdpEchoClientApplication:ScheduleTransmit()
2s UdpEchoClientApplication:Send()
2s UdpEchoClientApplication:Send(): Sent 1024 bytes to 10.1.1.2
2.00369s UdpEchoServerApplication:HandleRead(): Received 1024 bytes from 10.1.1.1
2.00369s UdpEchoServerApplication:HandleRead(): Echoing packet
2.00737s UdpEchoClientApplication:HandleRead(0x624290, 0x624ad0)
2.00737s UdpEchoClientApplication:HandleRead(): Received 1024 bytes from 10.1.1.2
10s UdpEchoServerApplication:StopApplication()
10s UdpEchoClientApplication:StopApplication()
UdpEchoClientApplication:DoDispose()
UdpEchoServerApplication:DoDispose()
UdpEchoClientApplication:~UdpEchoClient()
UdpEchoServerApplication:~UdpEchoServer()

在代码中添加日志信息

下面我们尝试直接通过代码来添加日志信息,还是以在scratch文件中的myfirst.cc为例:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

在myfirst.cc中,通过上面的语句,就定义了一个日志组件.

定义好组件后,我们直接在其后面添加语句:

NS_LOG_INFO ("Creating Topology");

该语句利用宏NS_LOG_INFO添加了一条LOG_INFO级别的日志. 日志内容就是"Creating Topology". 然后我们用下面的语句重新编译,并把之前设置的NS_LOG变量清空:

$ ./waf
$ export NS_LOG=

下面我们就可以运行代码了:

$ ./waf --run scratch/myfirst

会发现输出结果中并没有刚才我们设置的日志,因为我们还没有设置日志级别(要现实LOG_INFO级别的日志,必须设置相同或高于这一级别的日志组件). 因此,下面我们利用环境变量重新启用LOG_INFO级别的FirstScriptExample日志组件.

$ export NS_LOG=FirstScriptExample=info

现在再运行就能发现运行结果中出现了我们设置好的日志信息:

Waf: Entering directory `.../build'
Waf: Leaving directory `.../build'
'build' finished successfully (0.404s)
Creating Topology
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

命名空间

以下是examples/tutorial/first.cc脚本的一个命名空间声明:

using namespace ns3;

&ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3被实现在一个叫ns3的C++的命名空间中. 这个命名空间涵盖了所有与 &ThinSpace; n s − 3 &ThinSpace; \,ns{-}3\, ns3相关的声明. 上面这句声明就可以将ns3命名空间引入当前全局作用域.

main函数

声明主函数:

int
main (int argc, char *argv[])
{

下面这行代码将时间分辨率设置为1纳秒 ( n a n o s e c o n d ) (\mathbf{nanosecond}) (nanosecond)

Time::SetResolution (Time::NS);

下面两行代码用于启用两个内置于Echo Client和Echo Server应用中的日志组件,

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
  • LOG_LEVEL_INFO这一级别能够打印出在模拟过程中发送和接受数据包的信息.

模拟器 (Simulator)

设置完所有模拟过程后,就需要运行模拟器.
运行模拟器通过调用全局函数Simulator::Run来完成:

Simulator::Run ();

假设之前已经调用过如下方法:

serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
...
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));

实际上就在模拟器中预设了发生在1s、2s、10s等时刻的事件. 当Simulator::Run被调用,系统将开始查询预设的事件列表并按照时间顺序执行这些事件.

比如对于上面的这段代码,系统会先执行在第1s的事件,即让UDP回显服务端应用生效(这个事件可能也会预设很多其他事件). 然后运行第2s的事件,即让UDP回显客户端应用生效(同样,这个事件可能也会预设很多其他事件). 在回显客户端应用生效以后,就会开始模拟向服务端传送数据包的过程(将会引发更多的事件). 之后模拟器便进入空闲的状态,剩下的事件就是回显服务端和客户端的停止事件. 当这些事件执行完毕后,将没有其他事件进程,Simulator::Run返回,模拟过程结束.

模拟结束后,还需要对模拟过程中使用的内存进行清理. 通过调用全局函数Simulator::Destroy来完成这一任务. 当该方法被调用后,模拟器所有创建的对象将被销毁.

  Simulator::Destroy ();
  return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值