NS3学习笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

(不定时更新)
本人为NS3新手,对于C++和Python编程也堪堪入门而已,故尝试记录学习流程,以便强化学习效果。
使用的环境:
VMware16+Ubuntu18.04+ns-3.27
参考文献:
《ns-3网络模拟器基础与应用》马春光、《开源网络模拟器ns-3架构与实践》周迪之、官方Tutorial
除此之外,还有许多网络上已有的许多其他大佬的博客、文章等,感谢各位大佬的分享,让我们小白能少走许多弯路。


NS3安装与配置

安装NS3:
https://blog.csdn.net/Q_1849805767/article/details/106927985
使用vscode调试NS3:
https://blog.csdn.net/qQ240627995/article/details/124620259
安装AquaSimNG:
https://www.cnblogs.com/variablex/p/14141035.html
(教程里写需要降级gcc版本,但我安装时没有处理,初步使用没遇到什么问题,故暂且不管,后续遇到问题时再做处理)

NS-3.27的目录结构

此部分内容参考了:
https://blog.csdn.net/sinat_36418396/article/details/106605673
(感谢大佬)
ns-3.27目录如下图:
ns-3.27目录
我们主要关注的文件是"waf",主要关注的文件夹有"build"、“doc”、“examples”、“scratch”、“src”。

  • waf:编译工具,我们的大多编译都是使用这个工具完成。
  • build:编译目录,目录是ns-3.27编译目录,包含编译文件时使用的共享库和头文件(build/ns3),当我们想要清除之前编译内容,整体重新编译时,可以清除build文件夹,然后重新运行之前编译的那一套。
  • doc:保存了官方手册之类的内容,但其内容保存再各个文件中,不方便查看,这里更建议直接在ns3的官网上查看手册和教程。
    (ns-3.27官方教程网址:https://www.nsnam.org/docs/release/3.27/tutorial/html/index.html)
  • examples:保存了ns3自带的示例脚本,帮助初学者快速了解ns3
  • scratch:一般用于存放我们自己写的脚本文件,也可以把其他模块想要运行的例子拷贝到此目录下进行调试,当脚本在此目录下时,waf编译可以不写此目录,但是如果直接在其他模块里运行脚本的话,则需要附带脚本的完整路径。
  • src:存放了各种模块的源代码,其子目录名即为模块名。以ns3的核心模块为例:
    ns3核心模块目录
子目录或文件的名字含义
bindings与Python绑定
doc模块文档
examples示例脚本
helperhelper类的源代码
model模块的源代码
test测试用的示例代码
wscript用来注册模块中包含的源代码和使用其他模块情况

编译

1.初次编译

ns-3使用Waf工具来编译源代码。但Waf只能编译ns-3主项目。对于第一次下载ns-3源代码包的用户,一个比较好的方法是运行ns-3-allinone目录下的build.py脚本。这样可以一次性编译ns-3和其他辅助项目。
在ns-3-allinone目录下执行以下代码以初次编译:

build.py(可能会很久)
cd ns-3.27
./waf configure --enable-examples --enable-tests --build-profile=debug
./waf build

2.waf的使用

运行ns-3脚本需要用到Waf工具,进入ns-3-allinone下的ns-3.27目录,然后在该目录下运行第1章中提到的hello-simulator的示例脚本。

./waf --run hello-simulator

如果示例脚本在屏幕上打印出了“Hello Simulator”字符串,则表明安装成功。如果出错,则检查一下第三步中是否添加了“–enable-examples”参数或者清除ns-3.27下的build文件夹,然后按照安装ns-3那篇教程一步步执行。

如果用户改动了现有脚本或编写了一个新的脚本,则需要对ns-3项目重新编译。方法也很简单,直接在ns-3.27目录下运行“./waf”命令编译即可。实际上,waf是ns-3运行脚本、重新编译时最常用到的命令。build.py一般只在第一次下载ns-3代码库时使用。

修改编译配置

当我们修改了源代码或者想要修改配置选项的时候,需要对代码重新编译,相关指令如下:

./waf configure 参数
参数(注意,前面是双短线,中间是单短线)含义
–enable-examples编译时包含示例脚本
–enable-tests编译时包含测试脚本
–build-profile=编译模式,有两种:debug和optimized(默认为debug模式)
–out=更改waf生成文件的目录,使用时在"="后面写想要保存到目录即可(新手不建议改)

在每次configure时,建议清除之前编译结果:

./waf clean 或 ./waf distclean

以我粗浅的了解来看,这俩个用起来好像区别不太大,但大多教程里都写的是"./waf clean",因此怕出问题的话,使用前面那个就行了。

在别的目录下使用waf

waf命令的一个缺陷是只能在ns-3.27目录下执行。假设用户目前正在“ns-3.27/src/
internet/model/”目录下查看TCP源代码,想要运行“ns-3.27/examples/tcp/tcp-bulk-send.cc"脚本,则需要执行下面的代码:

../../.././waf --run tcp-bulk-send

其中"…"表示上一级目录的意思。但这样使用很不方便,解决方法:
1.保持一个终端始终在ns-3.27目录下运行,在该终端里进行编译。
2.使用shell函数来简化这个过程,在进入其他目录前,先在ns-3.27目录下执行一下语句:

export NS3DIR="$PWD"
function ns3waf { cd $NS3DIR && ./waf $* ; }

运行完此代码后,在此终端内"ns3waf"指令就相当于运行ns-3.27路径下的"./waf"指令,便于在别的目录下快速运行waf。但这个方法的缺点在于,每一次关闭该终端,“ns3waf”指令就会无效,需要重新执行上述代码才可使用。当然也可以把这个函数加在Linux的“.bashrc”文件中就不需要每次都重新设置了,但我是小白,怕弄坏环境,因此决定每次麻烦一点。
3.使用vscode调试:如果按照前文教程配置完成了vscode,则可以把想要调试的源文件复制到scratch目录下,然后就可以在vscode中进行调试,这里需要注意的是,有些模块源文件复制过来后需要修改头文件的路径,否则会报错。(由于本人也是vscode新手,因此并不知道有什么更方便的配置方法,(太难了))

保存waf输出结果

保存std::cout输出

./waf --run 你的ns3程序 1> 保存的文件名

保存NS_LOG输出

./waf --run 你的ns3程序 2> 保存的文件名

文件会保存在终端的当前运行路径下。


NS3中的常用概念

  • Node 节点:ns3用Node类抽象网络中的计算设备,可以理解为路由器,传感器之类的东西,通过向节点添加应用程序、协议栈之类的东西,使得其能够模仿某些设备的运行。
  • Application 应用:ns3中的Node类并没有真实的硬件设备和配套的驱动程序,因此想要仿真某些设备,还需要对其的功能进行模拟,Application类的作用就是对节点功能的模拟,比如收发数据包,以完成对某些设备的模拟。
  • Channel 信道:现实中的网络有有线信道和无线信道,在ns3中,一个Node将连接Channel对象上来实现通信,通过对Channel类的设置,可以模拟诸如衰减一类的信道属性,使其更能模拟现实信道。
  • NetDevice 网卡:ns3中网卡的抽象包括软件驱动程序和模拟硬件。一个网卡被“安装”在一个节点中,以使该节点能够通过信道与模拟中的其他节点通信。就像在真正的计算机中一样,一个节点可以通过多个NetDevices连接到多个信道,即NetDevice 类提供了管理Node和Channel对象的连接的方法,比如CsmaNetDevice被设计为与CsmaChannel一起工作,WifiNetNevice则被设计为与WifiChannel一起工作。
  • Helper 助手:在现实中硬件设备的使用往往需要一定的驱动程序和软件应用来实现,而在ns3中提供了Helper类辅助完成模块与模块之间的操作,比如使用使用GitHub上的开源水声模块AquaSimNG时,会有AquaSimHelper、AquaSimChannelHelper来辅助进行模块参数的设置以及水声信道的配置。

第一个NS3脚本 first.cc

在ns-3.27/examples/tutorial/中可以找到由官方提供的示例脚本first.cc
first.cc提供了一个包含两个节点的点对点连接的网络仿真,实现的是基于UDP协议的“回声功能”,即服务端把接收到的每一个数据包再送回客户端(UdpEcho),通常是用来获取网络信息、测试网络功能而使用的。
最上方的注释是对NS3的代码规范和权利的声明,如果我们希望将我们自己做的模块贡献给项目的话,就也需要按照其代码规范进行编程。除此之外,一些脚本还会在开头将其脚本打大致作用或者拓扑结构展现在开头。

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* 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
*/

接下来开始正式编程,首先要引用包含头文件:

#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"

"ns3/core-module.h"是ns3的核心模块需要的;"ns3/network-module.h"是ns3的网络模块; "ns3/applications-module.h"是ns3的应用模块需要的;一般我们自己写脚本时,这三个都是必要的。"ns3/internet-module.h"是ns3提供的因特网模块需要的;"ns3/point-to-point-module.h"是点对点通信模块需要的。
当我们自己写脚本想要使用其他模块时,一种方法是直接在ns-3.27/src/目录下直接查找,但由于各个模块分别在于各个文件夹中,不方便我们直观地去了解,因此我更建议去官网的文档搜索查看;当我们想要添加第三方模块或者自己编写的模块时,也需要将其放置在这个目录下,然后重新使用waf编译才行。
ns3官网文档Applications模块1
ns3官网文档Applications模块2
上图所示是官网文档中Applications模块的页面,以较好的排版向我们展示了该模块的功能和作用,有助于我们快速去了解一个模块的使用,个人感觉相较于直接去src目录下查看相应子目录要快捷的多。

通常而言,当一个C++工程是由多人开发的,那么工程越大,函数或变量名称重复的可能性就越大,为了避免使用各类库可能导致的名称冲突,C++引入命名空间来控制名称或标识符的作用域。在ns3中我们也要声明好使用ns3的命名空间以尽量避免这样的冲突:

using namespace ns3;

接下来我们就要打开ns3的Logging系统,这个日志模块用于输出调试信息,便于我们快捷获取程序调试时的有用信息。

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

其中 “FirstScriptExample” 是为这个日志模块声明的名字,我们可以通过引用这个名字来启用和禁用控制台消息日志记录。这里如果要使用NS3的log模块的话,这句代码就等于是“开关”,尽量写在各个函数的上方,比如紧跟着“using namespace ns3;”。这里声明的名字,是使用“ns3::LogComponentEnable”、“ns3::LogComponentDisable”或者NS _ LOG 环境变量时方便调用对应的模块,一般每个模块的源代码开头都会有这样一句,我们在管理相应模块的日志输出时就通过这个名字进行调用和配置。
下面我们来看main函数里面的内容:

  CommandLine cmd;
  cmd.Parse (argc, argv);

“CommandLine cmd;”意为开启命令行控制变量,在first.cc中并没有提供如何添加相应的配置项;
“cmd.Parse (argc, argv);”是解析命令行参数用的语句,有了这句我才能直接从命令行修改某些我们添加的参数;


番外1:以及知道了的,可以跳过
为了便于理解,这里我稍微示范一下如何使用:

  int num = 2;
  CommandLine cmd;
  cmd.AddValue ("nodesNum", "The number of nodes", num);
  cmd.Parse (argc, argv);

  NodeContainer nodes;
  nodes.Create (num);

这里我声明了int类型的变量num作为将要创建的节点数,默认其为2(即当不通过命令行修改时,其数值取2),使用时,我们在终端除入下面代码进行操作:

./waf --run "scratch/first --nodesNum=2"

这里我还是用两个节点,因为first.cc有些操作不是批量进行的,节点数量一多,会出一些小问题,不过这里就是示范操作而已。下图为终端输出:
终端输出
番外1结束!


  Time::SetResolution (Time::NS);

“Time::SetResolution (Time::NS);”这一句代码是设置给用户提供的计时单位,这里的“Time::NS”指的是纳秒,其他单位如下表所示

符号(“Time::”)含义
Y年/365天
D天/24小时
H小时/60分钟
MIN分钟/60秒
S
MS毫秒
US微秒
NS纳秒
PS皮秒
FS飞秒
LAST最后一个正常值的标记‎
AUTO使用Time::As()时自动设置
  LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);
  LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);

“LogComponentEnable (“UdpEchoClientApplication”, LOG_LEVEL_INFO);”这一句为开启"UdpEchoClientApplication"(基于UDP的客户端回声应用)的日志输出,并设置输出等级为LOG_LEVEL_INFO;“LogComponentEnable (“UdpEchoServerApplication”, LOG_LEVEL_INFO);”为开启"UdpEchoServerApplication"(基于UDP的服务端回声应用)的日志输出,并设置输出等级为LOG_LEVEL_INFO;
这里的"UdpEchoClientApplication"和"UdpEchoServerApplication"都是在相应模块内对其日志组件声明的名字,可以在对应模块的源代码内找到,比如在UdpEchoClient源代码里寻找其日志模块声明的名字
有些新手可能确实接触的很少,会好奇我是怎么找到这个地方的(我一开始学的时候真就是啥也不会,C++都是现学的😂),接下来就打断一下,我稍微以上面那个稍微做一个示范,已经知道的人可以跳过这一段,毕竟也不是啥很厉害的东西,就单纯是知道了就会了,不知道就不会而已。


番外2:(这里是基于VScode操作的,其他的软件我没用过,所以不太清楚)已经会的建议跳过
这里我用UdpEchoClient这个模块示例,首先找到使用这个模块的代码在哪里,有时候不是模块不是直接被使用的,而是通过helper助手类被使用的,比如first.cc中就是通过这个方式使用这个模块的:UdpEchoClient的助手类的使用
把我们的鼠标移到这个helper类的“类型”上,也就是"UdpEchoClientHelper"字段上,然后点击键盘的F12会进入它的声明所在的地方,然后我们随便找一个构建该类的函数,对这个函数再进行同样的操作,比如:
UdpEchoClientHelper类的一个构建函数
对其使用F12后我们就进入了该函数的实现代码所在的地方:
UdpEchoClientHelper类的一个构建函数的实现代码
我们的目的并不是照这个助手类的日志,而是找“UdpEchoClient”的日志,所以接下来我们需要找到“UdpEchoClient”的数据类型,比如上图中的
UdpEchoClientHelper类的一个构建函数的实现代码
这里就可以看到这样的字段,我们同样对其使用F12大法,进入其被声明的头文件
UdpEchoClient头文件的部分内容
然后随便找该头文件里的一个函数,使用F12大法即可进入该模块的源代码文件,比如这里我对“UdpEchoClient ();”函数使用F12大法,进入到了该模块的源代码文件。前面我们已经提到过了,对于日志模块的声明一般要在所有函数的上方,一般紧跟着“using namespace”函数,所以我们这里不去看其他内容,直接看到这个源代码文件的上方,即可找到其声明的名字:
UdpEchoClient日志组件声明的名字
其他模块也是使用类似的方法就可以找到了。
番外2结束!下面继续正题。


除了日志模块声明的名字以外,我们还要设置对应模块的日志等级,NS3提供了多种等级,如下表所示:

关键字含义
LOG_NONE无日志/不开启日志
LOG_ERROR仅限严重错误消息
LOG_LEVEL_ERRORLOG_ERROR 及其之上等级(LOG_NONE )
LOG_WARN警告消息
LOG_LEVEL_WARN警告消息及其之上的等级(LOG_LEVEL_ERROR )
LOG_DEBUGdebug消息
LOG_LEVEL_DEBUGdebug消息及其之上等级
LOG_INFO信息性消息,例如banners信息
LOG_LEVEL_INFO信息性消息及其之上等级
LOG_FUNCTION函数追踪
LOG_LEVEL_FUNCTION函数追踪及其之上等级
LOG_LOGIC‎函数内的控制流跟踪
LOG_LEVEL_LOGIC‎函数内的控制流跟踪及其之上等级
LOG_ALL打印所有等级
LOG_LEVEL_ALL打印所有等级
LOG_PREFIX_FUNC打印时加上函数前缀
LOG_PREFIX_TIME打印时加上时间前缀
LOG_PREFIX_NODE打印时加上节点前缀
LOG_PREFIX_LEVEL打印时加上日志等级前缀
LOG_PREFIX_ALL打印时加上所有前缀

接下来需要创建节点了:

  NodeContainer nodes;
  nodes.Create (2);

所有的节点需要被“存储”到一个容器中以便于管理,这里声明了一个名字是“nodes”的节点容器,然后使用节节点容器类提供的"Create"函数在容器内创建节点,括号里的数字是要创建的节点数量,这里就是创建两个节点。

接下来first.cc将要创建一个点对点的“虚拟网卡”(因为后续把这个点对点的助手类存储到了一个“网卡设备容器”里了,所以我就暂且这么说好了):

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

这里第一句是声明点对点模块的helper助手类,第二句是设置了传输的数据速率,第三句是配置了信道的时延,相应的语句可以在头文件里或者官网文档里查到(这里NS3使用了其自带的特殊数据类型,这些类型除了这里的StringValue,还有TimeValue、UintegerValue等等)。那么问题来了,该怎么去查某个属性用什么类型呢?


番外3:同理,已经会了的同学可以跳过
这里由于点对点是NS3自带的模块,因此可以结合网站文档快速查找,但有些第三方模块就无法在官方文档里查找了,因此这里我除了讲解如何在官方文档里查找以外,另外附带上我自己的小技巧(第三方模块应该也能用,我只试了AquaSimNG,其他的第三方模块没用过,但我觉得应该大差不差)
方法一:结合官方文档
首先我们使用F12大法,找到该助手类构建函数的实现代码,如下图所示:
PointToPointHelper的构建函数实现代码
可以看到这个PointToPointHelper可以管理四项子模块,这里first.cc只用了其中的设备模块“ns3::PointToPointNetDevice”和信道模块“ns3::PointToPointChannel”,(其实是另外两个我也没用过,所以忽略好了,反正这里不用)。好,现在我们知道了调用的子模块的名字,接下来去NS3的官方文档页面,在搜索栏里查找相应的语句,下图为查找PointToPointNetDevice的示例图
搜索PointToPointNetDevice
然后选择对应的模块“ns3::PointToPointNetDevice”,即可进入该模块的页面。然后我们点选其对应的构建函数项:PointToPointNetDevice构建函数项
进入其详细说明区域后,选择打开其源代码:
打开PointToPointNetDevice源代码
然后我们需要找到该源代码内的GetTypeId函数
GetTypeId函数
在该函数内我们不仅可以找到对应模块TypeId的设置,也可以找到其都添加了那些属性:
GetTypeId函数的一些解释
在这里就可以找到属性对应的参数类型了。
方法二:
首先我们使用F12大法,找到该助手类构建函数的实现代码,如下图所示:
PointToPointHelper的构建函数实现代码
这里我换一个示范,以"ns3::PointToPointChannel"为示范,在NS3的代码规范里,一般模块的TypeId就是其类的名称,但这里是字符串形式,我们没法直接使用F12大法,所以,我这提供一个小技巧:
我们先将其名字复制到代码区的一个空行,如下图所示:
小窍门
这里先忽略后面的报错,因为我们只是为了使用F12大法而已,用完以后就要把这一行内容删掉的。将其摘出到外面,你会发现你可以使用F12大法了,那还等什么,直接F12追到它的源文件里!
进入源文件(.cc文件,不是.h头文件,我们要看的是GetTypeId的实现部分)以后,找到GetTypeId函数,这里要注意分辨,有的源文件里不止对一个模块进行声明和实现,要分辨清楚再查看。
GetTypeId函数的一些解释
最后,我再提醒一遍,一定要记得用完把之前多写的那一行内容删掉,不然会报错的。
番外3结束。


接下来,我们需要将配置好的点对点通信的“网卡”安装到节点上:

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

第一句是声明了一个“网卡设备”容器,这里NS3使用这些“容器”以便于后续操作和管理;第二句是把我们之前配置好的点对点通信的“网卡”安装到我们创建的的节点上(这就就是直接对容器进行安装,简单快捷的实现了所有节点的批量安装),与此同时,我们也将这些安装好的网卡设备“存储”到之前声明的“网卡设备”容器中,以便后续操作使用。
下一步,当我们要基于IPv4给节点分配地址时,在使用相关IPv4模块时有一个前提,那就是我们的节点必须得具有“网络堆栈”:

  InternetStackHelper stack;
  stack.Install (nodes);

第一句就是声明一个因特网堆栈的助手类,第二句就是将其安装到节点上;其实因特网堆栈有时不是必要的,比如有时候你只想实现简单的Socket通信时,就可以只用Socket模块的东西,而不去管这个因特网堆栈。甚至于你同时使用的时候会报错“重复聚合”(我还没搞明白具体是哪里导致的这个问题)
在此之后我们就能进行给节点分配ipv4地址等操作了:

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

  Ipv4InterfaceContainer interfaces = address.Assign (devices);

上述代码中,第一句为声明IPv4地址助手,第二句即为设置地址是从10.1.1.0开始,子网掩码为255.255.255.0;第三句依旧使用了NS3惯用的容器类,创建一个IPv4的交互口,并使用器将我们设置好的地址分配给各个节点的网卡设备。
这样我们就完成了first.cc中NS3路由相关的设置,接下来就要配置我们要仿真实现的功能,也就是节点之间的通信是“干啥的”,为此我们就需要设置服务端和客户端两方的“应用”了(first.cc使用的是回声应用,即“收到什么就回复什么”)。
首先配置的是服务端的回声应用,这里NS3已经将回声应用打包成模块了,我们只要将其“安装到”相应节点上即可,当然如果你想用以其他官方没有打包好的应用,可能就需要你自己先去写一个模块来实现了。

  UdpEchoServerHelper echoServer (9);

  ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
  serverApps.Start (Seconds (1.0));
  serverApps.Stop (Seconds (10.0));

上述代码中,第一句代码为声明一个基于UDP的回声服务端助手,括号中的“9”代表使用的端口号是9;第二句则是NS3惯用思路,利用容器批量管理应用(只不过我们这里一共就俩节点而已),首先声明好容器,并将服务端应用安装到相应的节点上,这里是把服务端应用安装到了第2个节点上(索引是1,第一个节点索引是0),“nodes.Get (?)”语句就是从容器中提取某个节点,“?”部分是该节点在这个容器里的索引值,该函数实际上是把通过容器把该节点的智能指针返回出来。后面第三句、第四句就是设置应用在什么时候开启和关闭,这里设置的是开始仿真的第1秒开始,第10秒结束。(Seconds(?)是NS3提供的一种生成时间参数类型函数,这个函数返回以秒为单位的时间参数)
Seconds函数
在我们配置好服务端应用之后,下一步开始配置客户端应用。

  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));

这里第一句为声明基于UDP的回声客户端助手,并设置服务端的地址和端口(我们前面已经说了,我们把第2个节点当作服务端,并且在“存储”进容器时我们都没有去改变其索引的顺序,因此第二个节点的ip交互口在其容器内的索引与该节点在节点容器的索引一样,都是1,因此这里我们使用“interfaces.GetAddress (1)”获取其IPv4地址;至于端口,我们都知道,要想让应用能正确的进行通信,其对应的端口必一致,打个比方就是A公司要去A的码头才能取得A公司订的货,所以我们这里端口与之前设置保持一致,设为9)
第二至第四句则为设置客户端回声应用的相关属性,与之前配置点对点模块的helper助手类的相关属性差不多,这里就不再赘述了。而最后三句与之前配置服务端时差不多,因此也不多赘述了。
最后,我们需要管理启动仿真以及关闭仿真相关代码:

  Simulator::Run ();
  Simulator::Destroy ();

第一句为启动仿真,第二局为结束仿真时清空相应事件等,以免占用内容和干扰下一次仿真的运行。除了这两个以外,常用的还有“Simulator::Stop(?);”这样的语句来控制仿真时间,比如下面一个示例语句就是强迫Simulator::Run ()在运行到仿真第200秒时强制停止。

Simulator::Stop(Seconds(200));

至此,我们完成了这两个节点的点对点通信的全部所需内容,这里我重复一下first.cc整体代码的思路:

  1. 引用相关库
  2. 使用命名空间
  3. 开启日志模块
  4. 配置命令行修改参数
  5. 设置时间分辨率
  6. 设置某些模块日志输出等级,不写则默认不输出
  7. 创建节点
  8. 创建虚拟网卡(包含信道的相关配置)
  9. 配置IPv4协议
  10. 配置服务端和客户端应用
  11. 运行仿真
  12. 结束仿真并清空
    最后,由于不会上传附件,所以我就把完整first.cc代码粘贴在下方好了,虽然感觉大家都不太需要,不过有的话,会显得我这篇文章完整一些,嘿嘿。
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
 * 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
 */

#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");

int
main (int argc, char *argv[])
{
  CommandLine cmd;
  cmd.Parse (argc, argv);
  
  Time::SetResolution (Time::NS);
  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;
}

  • 22
    点赞
  • 127
    收藏
    觉得还不错? 一键收藏
  • 14
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值