【声明】允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。
http://yangfei1.blog.51cto.com/1471532/368589
http://yangfei1.blog.51cto.com/1471532/368591
转载的文章中给的原文地址已经失效,我这里重新整理了原文地址,方便大家查看对照,如果也失效了,可以直接去官网找Tutorial
官网的英文原文地址:
ns-3 Tutorial https://www.nsnam.org/docs/tutorial/html/
原文目录如下:
翻译: 陈杰 刘小洋 [email protected] (百思论坛)
校稿: Xiaochuan Shen [email protected]
编辑: ProbibidoAmor [email protected]
在本节中,我们将回顾一些常用的网络术语,以及它们在ns-3的特定含义。
在因特网术语中,任何一台连接到网络的计算设备被称为主机,亦称为终端。 ns -3是一个网络模拟器,而非一个专门的因特网模拟器,为此我们避开术语“主机”,因为这个词太容易让人联想到因特网和及其相关协议。因此,我们选用了一个来源于图论,在其他网络模拟器中亦广泛使用的术语:节点。
ns-3中基本计算设备被抽象为节点。节点由C++中的Node类来描述。Node类提供了用于管理仿真器中网络组件表示的各种方法。
你应该将节点设想为一台可以添加各种功能的计算机。为了使一台计算机有效地工作,我们可以给它添加应用程序,协议栈,外设卡及驱动程序等。NS3采用了与此相同的模型。
计算机软件通常可分为两大类:
系统软件根据计算模型配置和管理计算机中的各种资源,例如内存,处理器周期,硬盘,网络等。系统软件通常并不直接使用这些资源来完成用户任务。用户往往需要运行应用程序来完成一些特定的任务,而应用程序需要使用由系统软件控制的资源。
通常,系统软件和应用软件的界线表现为特权级的变化,而这种变化是通过操作系统的自陷功能(operating system traps)来实现的。在ns-3中并没有真正的操作系统的概念,更没有特权级别或者系统调用的概念。然而,我们有应用程序的概念。正如“现实世界”中在计算机上运行应用程序以执行各种任务一样,ns-3仿真环境中的应用程序在节点上运行来驱动模拟过程。
在ns-3中,需要被仿真的用户程序被抽象为应用。应用在C++中用Application类来描述。这个类提供了管理仿真时用户层应用的各种方法。开发者应当用面向对象的方法自定义和创建新的应用。
在本教程中,我们会使用Application类的实例:UdpEchoClientApplication 和UdpEchoServerApplication。也许你已经猜到了,这些应用程序包含了一个client/server应用来发送和回应仿真网络中的数据包。
在现实世界中,人们可以把计算机连接到网络上。通常我们把网络中数据流流过的媒介称为信道。当你把以太网线插入到墙壁上的插孔时,你正通过信道将计算机与以太网连接。在ns-3的模拟环境中,你可以把节点连接到代表数据交换信道的对象上。在这里,基本的通信子网这一抽象概念被称为信道,在C++中用Channel类来描述。
Channel类提供了管理通信子网对象和把节点连接至它们的各种方法。信道类同样可以由开发者以面向对象的方法自定义。一个信道实例可以模拟一条简单的线缆(wire),也可以模拟一个复杂的巨型以太网交换机,甚至无线网络中充满障碍物的三维空间。
我们在本教程中将使用几个信道模型的实例,包括:CsmaChannel, PointToPointChannel和WifiChannel。举例来说,CsmaChannel信道模拟了用于一个可以实现载波侦听多路访问通信子网中的媒介。这个信道具有和以太网相似的功能。
以前,如果想把一台计算机连接到网络上,你就必须买一根特定的网络线缆,并在你的计算机上安装称为外设卡的硬件设备。能够实现网络功能的外接卡被称为网络接口卡(网卡)。现在大多数计算机出厂时已经配置了网卡,所以用户看不到这些的模块。
一张网卡如果缺少控制硬件的软件驱动是不能工作的。在Unix(或者Linux)系统中,外围硬件被划为 “设备”。设备通过驱动程序来控制,而网卡通过网卡驱动程序来控制。在Unix和Linux系统中,网卡被称为像eth0这样的名字。
在ns-3中,网络设备这一抽象概念相当于硬件设备和软件驱动的总和。NS3仿真环境中,网络设备相当于安装在节点上,使得节点通过信道和其他节点通信。像真实的计算机一样,一个节点可以通过多个网络设备同时连接到多条信道上。
网络设备由C++中的NetDevice类来描述。NetDevice类提供了管理连接其他节点和信道对象的各种方法,并且允许开发者以面向对象的方法来自定义。我们在本教程中将使用几个特定的网络设备的实例,它们分别是CsmaNetDevice, PointToPointNetDevice, 和 WifiNetDevice。正如以太网卡被设计成在以太网中工作一样,CsmaNetDevice被设计成在csma信道中工作,而PointToPointNetDevice 在PointToPoint信道中工作,WifiNetNevice在wifi信道中工作。
在现实的网络中,你会发现主机已装有(或者内置)的网卡。在ns-3中你会发现节点附加了网络设备。在大型仿真网络中,你需要在节点、网络设备和信道之间部署许多连接。
既然把网络设备连接到节点、信道,配置IP地址等等事情在ns-3是很普遍是任务,那么我们干脆提供了“拓扑生成器”来使这个工作变得尽可能的容易。举例来说:创建一个网络设备,配置一个MAC地址,把此网络设备装载到节点上,设置节点的协议栈,以及连接网络设备到一个信道,这些事情都需要许多分立的ns-3核心操作。而当需要把许多设备连接到多点信道,在网际间将单个网络进行连接,则需要对ns-3核心进行更多更多的分立操作。我们提供了拓扑生成器来整合这些大量分立的步骤,使其成为一个简单易用的操作。很明显,这样做可以极大地方便用户。
如果你按照前面所述下载了ns3仿真系统,你会发现在你的根目录下的“repos”的文件夹里有一份ns-3的发行版。进入那个目录,你可以看到一个如下的文件目录:
进入examples/tutorial目录。你会发现一个叫first.cc的文件。这一个脚本会在两个节点间创建一个简单的点到点的连接,并且在这两个节点之间传送一个数据包。让我们一行一行的分析一下这个脚本,下面就用你钟爱的编辑器打开first.cc吧。
在文件中的第一行是emacs模式行。这行告诉了emacs我们在源代码中使用的预定格式(代码风格)
/* -*- Mode:C++; c-file-style:''gnu''; indent-tabs-mode:nil; -*- */
这向来是一个有点争议的主题,所以我们不妨现在就把它提出来。像许多大型的软件开发项目一样,ns-3项目采用了一套所有贡献代码必须遵守的代码风格。如果你想给ns-3贡献代码,你最终需要遵守在文件doc/codingstd.txt中说明的或者遵守在ns-3项目的网页here上显示的代码标准。
我们建议你在使用ns-3的代码的时候最好适应这个标准。所有的开发团队和贡献者带着不同程度的抱怨来做到了这一点。如果你使用emacs编辑器的话,emacs模式行会使代码格式符合规范更加容易。
Ns-3仿真器使用了GNU General Public License许可。你会在ns-3的每一个文件头看到相应的GNU法律条文。通常你会在GPL内容的上方看到一个相关机构的版权声明,而在GPL内容的下方会有相应的作者列表。
/*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
4.2.2 模块包含
代码一般是以一系列的include声明开始的:
#include "ns3/core-module.h"
#include "ns3/simulator-module.h"
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
为了帮助高层的脚本用户处理大量的系统中的include文件,我们把所有的包含文件,根据模块功能,进行了大致的分类。我们提供了一个单独的include文件,这个文件会递归加载所有会在每个模块中会被使用的include文件。我们给你提供了按大致功能分类的一组include文件,在使用时只需选择包含这几个包含文件(include文件),而非考虑复杂的依赖关系,在寻找所需要的头文件上花费不必要的时间。这不是最有效地方法但很明显让编写脚本文件容易多了。
在编译的过程中,每一个ns-3的include文件被放在build目录下一个叫ns3的目录中,这样做可以避免include文件名的冲突。ns3/core-module.h
与src/core目录下的ns-3模块
相对应。如果你查看ns3目录会发现大量的头文件。当你编译时,Waf会根据配置把在ns3目录下的公共的头文件放到build/debug 或者build/optimized目录下。Waf也会自动产生一个模块include文件来加载所有的公共头文件。
当然,既然你一步步遵循着这个手册走的话,你可能已经使用过如下命令:
./waf -d debug configure
来配置工程以完成调试工作。你可能同样使用了如下命令:
./waf
来编译ns-3。现在如果你进入../../build/debug/ns3 目录的话你会发现本节开头提到的四个头文件。你可以仔细看一下这些文件的内容,你会发现它们包含了在相关模块中的所有的include文件。
在first.cc脚本的下一行是namespace的声明。
using namespace ns3;
Ns-3工程是在一个叫做ns3的C++ 命名空间中实现的。这把所有与ns3相关的声明,集中在一个与全局命名空间相区别的命名空间中。我们希望这样会给ns3与其他代码的集成带来好处。C++用“使用”语句(using)来把ns-3 namespace引入到当前的(全局的)声明域中。这个声明就是说,你不用为了使用ns-3的代码而必须在所有的ns-3代码前打上ns3:: 作用域操作符。如果你对命名空间并不熟悉,请查阅任何的C++手册并比较ns3命名空间和标准”std”命名空间的使用;通常你可以在cout和streams的讨论中看到命名空间的相关部分。
下一句脚本如下:
NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");
我们认为本节是谈论Doxygen 文件系统的好地方。如果你查看ns-3项目的网站(ns-3 project),你会在导航栏中发现一个指向“Doxygen (ns-3-dev)” 的链接。打开链接,你会被带到当前开发版文档页面。同时还有一个“Doxygen (stable)”得链接,它是最新的ns-3稳定发行版的文档页面。
在左边,你能找到一个文档结构的图形化目录。在ns-3的导航树中NS-3 Modules这本 “书”是一个开始的好地方。如果你展开“Modules”目录会看到ns-3 模块文件的列表。这里的“模块”一级中的内容和上面讨论的模块include文件中的内容一一对应。Ns-3的日志子系统将会在Using the logging Module 章节进行讨论,但你可以在核心模块(core module)的中找到相关的描述,所以继续前进打开核心(Core)这个文件节点。接着展开调试(“Debugging”)节点,选择日志(Logging)页面。
你现在应该看一下日志模块的Doxygen文件。在页面顶上的#defines列表中你会看到NS_LOG_COMPONENT_DEFINE的条目。在你想要要深入了解之前,最好仔细看看日志模块的详细说明,这样你能够了解一下总体的流程。你可以继续下拉或者选择在collaboration diagram下的“更多”链接来达到目的。
一旦对日志模块有了整体的概念,那么请仔细看针对 NS_LOG_COMPO-NENT_DEFINE的文档。我不想把整个文件复制到这里,但是可以总结一下大致的意思,这一行声明了一个叫FirstScriptExample的日志构件,通过引用FirstScriptExample这个名字的操作,可以实现打开或者关闭控制台日志的输出。
下面的脚本是:
int
main (int argc, char *argv[])
{
这就是你的脚本程序的主函数的声明。正如任何其它C++程序一样,你需要定义一个会被第一个执行的主函数。你的ns-3脚本没有什么特别的,就和一个普通的C++程序一样。
下面两行脚本是用来使两个日志组件生效的。它们被内建在Echo Client 和Echo Server 应用中:
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);
如果你已经读过了日志组件文档,你会看到日志分成一系列详尽级别。这两行代码将回显clients和server的日志级别设为”INFO”级。这样,当仿真发生数据包发送和接受时,对应的应用就会输出相应的日志消息。
关于日志模块,我们在后续内容中会详细介绍。现在我们直接着手创建拓扑和运行仿真。我们使用拓扑生成器对象来使这件事尽可能的容易。
在我们的脚本中的下面两行将会创建ns-3节点对象,它们在仿真中代表计算机。
NodeContainer nodes;
nodes.Create (2);
在继续之前我们先找到NodeContainer类的文档。进入一个现成的类的说明文档可以通过在Doxygen页面的类标签中做到。如果你没有关闭上一节中打开的Doxygen页面的话,只要上拉到页面的顶部并选择类标签,就可以看见一个新的标签,类列表,出现。在标签下面你看看到所有的ns-3的类列表。往下翻,找到ns3::NodeContainer。当你找到它后,点击它然后进入这个类的说明文档。
你可能回忆起节点概念是我们的一个关键抽象概念。节点代表一台能够加入诸如协议栈,应用以及外设卡等等的东西的计算机。NodeContainer 的拓扑生成器提供一种简便的方式来创建、管理和使用任何节点对象,我们用这些节点来运行模拟器。上面的第一行只是声明了一个名为”nodes”的NodeContainer。第二行调用了nodes对象的Create()方法创建了两个节点。正如Doxygen所描述的那样,这个容器调用NS-3种的内部函数来产生两个节点对象,并把指向这两个对象的指针存储在系统之中。
在脚本中他们所代表的节点什么都没有做。构建拓扑的下一步是把我们的节点连接到一个网络中。我们所支持的最简单的网络形式是一个在两个节点之间单独的point-to-point连接。我们在此会构建一个此类连接。
现在我们来以一种你以后将会非常熟悉的方式来构建一个点到点的连接。我们使用拓扑生成器来完成创建,连接的底层工作。回忆一下我们的两个关键抽象概念:网络 设备、信道。在真实的世界中,这些东西大致相当于外设卡和网线。需要说明的是这两样东西紧密的联系在一起而不能够把它们交互地使用(比如以太网设备和无线 信道就不能一起使用)。拓扑生成器遵循了这种紧密的连接,因此你在这个脚本中仅需使用PointToPointHelper来配置和连接ns-3的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的一个属性相对应。如果你查看Doxygen中的ns3::PointToPointNetDevice 类,并阅读GetTypeId 方法的文档,你会发现设备定义了一系列属性,在这些属性中就有“DataRate”。大部分用户可见的ns-3对象都有类似的属性列表。正如你在下面的部分会看到的一样,我们使用了这个机制以方便地配置仿真器,而不用重新对源代码进行编译。
与PointToPointNetDevice上的“DataRate”类似,PointToPointChannel也有一个Delay属性:
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));
告诉PointToPointHelper使用”2ms”(2毫秒)作为每一个被创建的点到点信道传输延时值。
现在我们有一个包含两个节点的NodeContainer对象。我们有一个准备在两个节点之间创建PointToPointNetDevices和wirePointToPointChannel对象的PointToPointHelper对象。正如我们使用NodeContainer拓扑生成器对象来为我们的模拟创建节点,我们会让PointToPointHelper来做关于创建,配置和安装设备的工作。我们需要一个所有被创建的NetDevice对象列表,所以我们使用一个NetDeviceContainer对象来存放它们,就像我们使用一个NodeContainer对象来存放我们所创建节点。下面两行代码:
NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);
会完成设备和信道的配置。第一行声明了上面提到的设备容器,第二行完成了主要工作。PointToPointHelper的Install()方法以一个NodeContainer对象作为一个参数。在Install()方法内,一个NetDeviceContainer被创建了。对于在NodeContainer 对象中的每一个节点(对于一个点到点链路必须明确有两个节点),一个PointToPointNetDevice被创建和保存在设备容器内。一个PointToPointChannel对象被创建,两个PointToPointNetDevices与之连接。当PointToPointHelper对象创建时,那些在生成器中就被预先地设置的属性被用来初始化对象对应的属性值。
当调用了pointToPoint.Install(nodes)后,我们会有两个节点,每一个节点安装了点到点网络设备,在它们之间是一个点到点信道。两个设备会被配置在一个有2毫秒传输延时的信道上以5M比特每秒的速率传输数据。
我们现在已经配置了节点和设备,但是我们还没有在节点上安装任何协议栈。下面两行代码完成这个任务:
InternetStackHelper stack;
stack.Install (nodes);
类InternetStackHelper 是一个安装PointToPointHelper 对象和点到点网络设备的网络协议栈的拓扑生成器类。其中Install()方法以一个NodeContainer 对象做为一个参数,当它被执行后,它会为每一个节点容器中的节点安装一个网络协议栈(TCP,UDP,IP等等)。
下面我们需要为节点上的设备设置IP地址。我们也提供了一个拓扑生成器来管理IP地址的分配。当执行实际的地址分配时唯一用户可见的API是设置基IP地址和子网掩码。
在我们的范例脚本文件first.cc的下两行代码
Ipv4AddressHelper address;
address.SetBase ("10.1.1.0", "255.255.255.0");
声明了一个地址生成器对象,并且告诉它应该开始从10.1.1.0开始以子网掩码为255.255.255.0分配地址。地址分配默认是从1开始并单调的增长,所以在这个基础上第一个分配的地址会是10.1.1.1,紧跟着是10.1.1.2等等。底层ns-3系统事实上会记住所有分配的IP地址,如果你无意导致了相同IP地址的产生,这将是一个致命的错误(顺便说一下,这是个很难调试正确的错误)。
下面一行代码,
Ipv4InterfaceContainer interfaces = address.Assign (devices);
完成了真正的地址配置。在ns-3中我们使用 Ipv4Interface对象将一个IP地址同一个设备关联起来。正如我们有时候需要一个被生成器创建的网络设备列表一样,我们有时候需要一个 Ipv4Interface对象的列表。Ipv4InterfaceContainer提供了这样的功能。
现在我们有了一个安装了协议栈,配置了IP地址类的点到点的网络。这时我们所要做的事情是运用它来产生数据通信。
另一个ns-3系统的核心抽象是Application类。在这个脚本中我们用两个特定的ns-3核心 Application类:UdpEchoServerApplication
和UdpEchoClientApplication
。正如我们先前声明过的一样,我们使用生成器对象来帮助配置和管理潜在的对象。在这里,我们用UdpEchoServerHelper
和UdpEchoClientHelper
对象使我们的工作更加容易点。
下面在first.cc脚本中的代码被用来在我们之前创建的节点上设置一个UDP 回显服务应用。
UdpEchoServerHelper echoServer (9);
ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
上面一片代码中的第一行声明了UdpEchoServerHelper。 像往常一样,这个并非应用本身,这是一个用来帮助创建真正应用的对象。我们约定在生成器中放置必需的属性。本例中,除非我们告知生成器服务器和客户端所共 知的一个端口号,否则这个生成器是不会起任何作用的。我们并没有随机选择,而是把这个端口号作为生成器构造函数的一个参数。只要你愿意,你就能够使用 SetAttribute设置“Port”参数到另一个值。
同其它生成器对象类似,UdpEchoServerHelper对象有一个Install方法。实际上是这个方法的执行,才初始化回显服务器的应用,并将应用连接到一个节点上去。有趣的是,安装方法把NodeContainter当做一个参数,正如我们看到的其他安装方法一样。这里有一个C++隐式转换,此转换以nodes.Get(1)的结果作为输入,并把它作为一个未命名的NodeContainer的构造函数的参数,最终这个未命名的NodeContainer被送入Install方法中去。如果你曾迷失于在C++代码中找到一个编译和运行良好的特定方法签名(signature),那么就寻找这些内在的转换。
我们现在会看到echoServer.Install将会在管理节点的NodeContainer容器索引号为1的机节点上安装一个UdpEchoServerApplication。安装会返回一个容器,这个容器中包含了指向所有被生成器创建的应用指针。
应用对象需要一个时间参数来“开始”产生数据通信并且可能在一个可选的时间点“停止”。我们提供了开始和停止的两个参数。这些时间点是用ApplicationContainer的方法Start和Stop来设置的。这些方法以”Time”对象为参数。在这种情况下,我们使用了一种明确的C++转换序列来获得C++双精度(double)的1.0并且用一个Seconds转换(cast)来把它转换到ns-3的Time对象。需要注意的是转换规则是模型的作者所控制,并且C++也有它自己的标准,所以你不能总是假定参数会按照你的意愿顺利地转换。下面两行,
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
会使echo服务应用在1s时开始(生效)并在10s时停止(失效)。既然我们已经声明了一个模拟事件(就是应用的停止事件)在10s时被执行,模拟至少会持续10s。
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的构建过程中被设置的。按照生成器的构造函数的格式,我们把”RemoteAdress”和”RemotePort”属性传递给了生成器(实际上是作为生成器构造函数的两个必须参数传递的)。
回忆一下我们使用Ipv4InterfaceContainer
来追踪我们配置给设备的IP地址。在界面容器中位置零的界面对象将会和在节点容器中的位置零的节点对象对应。同样在界面容器中位置一的界面对象将会和在节点容器中的位置一的节点对象对应。所以,在上面的第一行代码中,我们创建了一个生成器并告诉它设置客户端的远端地址为服务器节点的IP地址。我们同样告诉它准备发送第二个数据包到端口9。
那个“MaxPackets”属性告诉客户端我们所允许它在模拟期间所能发送的最大数据包个数。“Interval”属性告诉客户端在两个数据包之间要等待多长时间,而“PacketSize”属性告诉客户端它的数据包应该承载多少数据。本例中,我们让客户端发送一个1024字节的数据包。
正如echo服务端一样,我们告诉echo客户端何时来开始和停止,但是这里我们使客户端在服务端生效1s后才开始(在模拟器中时间2s的时候)。
下面我们所需要做的就是运行模拟器,这是用全局函数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.0秒,2.0秒,和10.0时预设了时间的发生。当Simulator::Run被调用时,系统会开始遍历预设事件的列表并执行。首先它会在1.0s时运行事件,这个事件会使echo服务端应用生效(这个事件会预设更多的其他事件)。接下来仿真器会运行在t=2.0秒时的事件,即让echo客户端应用开始。同样的,这个事件可能会预定更多的其他事件。在echo客户端应用中的开始事件的执行会通过给服务端传送一个数据包来开始仿真的数据传送阶段。
发送一个数据包给服务端会引发一系列更多的事件。这些事件会被预设在此事件之后,并根据我们已经在脚本中设定的时间参数来执行数据包的应答。
其实,我们只发送了一个数据包(回忆一MaxPackets属性被设置为一),在此之后,那个被单独的客户端应答请求所引发的连锁反应会停止,并且模拟器会进入空闲状态。当这发生时,生下来的事件就是服务端和客户端的Stop事件。当这些事件被执行后,就没有将来的事件来执行了,函数Simulator::Run会返回。整个模拟过程就结束了。
下面剩下的事情就是清理了。这个通过调用全局函数Simulator::Destroy
来完成。当生成器函数(或者低级的ns-3代码)被执行后,生成器安排的钩子函数就被插入到模拟器中来销毁所有被创建的对象。你自己并不需要追踪任何对象,你所需要做的仅仅是调用Simulator::Destroy并且退出。ns-3系统会帮你料理这些繁杂的任务。在first.cc脚本中剩下的代码如下:
Simulator::Destroy ();
return 0;
}
我们已经让构建你自己的脚本变得非常省事。你所需要做的仅仅是把你的脚本放到scratch目录下,并运行waf,这样你的脚本就会被编译。在回到高层目录后复制examples/tutorial/first.cc到scratch目录下
cd ..
cp examples/tutorial/first.cc scratch/myfirst.cc
现在使用waf命令来创建自己的第一个实例脚本:
./waf
你应该可以看到消息报告说你的myfirst范例被成功编译了。
Waf: Entering directory
/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
/home/craigdo/repos/ns-3-allinone/ns-3-dev/build’
[614/708] cxx: scratch/myfirst.cc -> build/debug/scratch/myfirst_3.o
[706/708] cxx_link: build/debug/scratch/myfirst_3.o -> build/debug/scratch/myfirst
Waf: Leaving directory
‘build’ finished successfully (2.357s)
现在你能够运行这个例子(注意如果你在scratch目录编译了你的程序,你必须在scratch目录外运行它):
./waf --run scratch/myfirst
你应该能看到一些输出:
Waf: Entering directory
/home/craigdo/repos/ns-3-allinone/ns-3-dev/build'
/home/craigdo/repos/ns-3-allinone/ns-3-dev/build’
Waf: Leaving directory
‘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客户端记录了它已经接收到了从服务端发送过来的回显数据包。