
一、NS3基础
本章参考书籍:《ns-3网络模拟器基础与应用》
1、NS3模块划分
NS3中拥有以下模块:
core:ns-3 的内核模块,实现了 ns-3 的基本机制,如智能指针 (Ptr)、属性(attribute)、回调(callback)、随机变量(random variable)、日志(logging)、追踪(tracing)和事件调度(event scheduler)等内容。
network:网络数据分组(packet)的模块,一般仿真都会使用。
Internet:实现了关于TCP/IPv4和IPv6的相关协议族,包括 IPv4、IPv6、ARP、UPP、TCP、邻居发现和其他相关协议,目前大多数网络都是基于Internet协议栈的。
applications:几种常用的应用层协议。
mobility:移动模型模块,当前移动设备普及、移动网络盛行,许多网络场景都离不开节点的移动。
topology-read:读取指定轨迹文件数据,按照指定格式生成相应的网络拓扑。
energy:能量管理模块,移动设备面临的是能量受限问题,因此在研究移动网络协议时能量不得不考虑。
status:统计框架模块,方便ns-3仿真的数据收集、统计和分析。
tools:统计工具,包括统计作图工具gnuplot的接口和使用。
visualizer:可视化界面工具PyViz。
netanim:动画演示工具NetAnim。
propagation:传播模型模块。
flow-monitor:流量监控模块。
还有一些常用的网络模块:
point-to-point:实现了点到点通信的网络。
CSMA:实现了基于IEEE 802.3的以太网络,包括MAC层、物理层和媒体信道。
Wi-Fi :实 现 基 于 IEEE 802.11a/b/g的无线网络,可以是有基础设施的也可以是ad hoc网络。
mesh:实现基于IEEE 802.11s的无线mesh网络。
wimax:实现了基于IEEE 802.16标准的无线城域网络。
UAN:ns-3 的水声通信 网络模块,能仿真水下网络场景,实现了信道、物理层和MAC层。
对新技术的支持:
click:ns-3中集成的可编程模块化的软件路由器(the click modular router)。
openflow:在ns-3中仿真OpenFlow交换机。
MPI:并行分布式离散事件仿真,ns-3 实现了标准的消息传递接口(MPI,message passing interface)。
2、关键概念
2.1、节点Node
节点由C++中的Node类来描述。Node类提供了用于管理仿真器中网络组件表示的各种方法。例如 NodeContainer 类,用于追踪一组节点指针。通常,ns-3助手一次在多个节点上工作,如果一个设备助手可能在大量的相同节点上安装设备。助手采用的安装方法通常需要 NodeContainer 作为一个参数。
NodeContainers承载多个Ptr<Node>
用来指向参考节点。下面两行将会创建ns-3节点对象,它们在仿真中代表计算机:
NodeContainer nodes;
nodes.Create (2);
NodeContainer的拓扑生成器提供了一种简便的方式来创建、管理和使用任何节点对象,本文用这些节点来运行模拟器。上面的第一行只是声明了一个名为“nodes”的NodeContainer。第二行调用了nodes 对象的Create()方法创建了2个节点。这个容器调用ns-3中的内部函数来产生2个节点对象,并把指向这2个对象的指针存储在系统中。
2.2、应用Application
计算机软件通常分为系统软件和应用软件两大类。应用程序需要使用由系统软件控制的资源,而系统软件通常并不直接使用这些资源来完成用户任务。用户往往需要运行应用程序来完成一些特定的任务。系统软件根据计算模型配置和管理计算机中的各种资源,例如内存、处理器周期、硬盘、网络等。
ns-3仿真环境中的应用程序在节点上运行来驱动模拟过程。
在ns-3中,需要被仿真的用户程序被抽象为应用。应用在C++中用 Application类来描述。这个类提供了管理仿真时用户层应用的各种方法。开发者应当用面向对象的方法自定义和创建新的应用。
2.3、信道channel
通常把网络中数据流流过的媒介称为信道。当你把以太网线插入 到墙壁上的插孔时,你正通过信道将计算机与以太网连接。在ns-3的模拟环境中,你可以把节点连接到代表数据交换信道的对象上。在这里,基本的通信子网这一抽象概念被称为信道,在C++中用channel类来描述。
Channel类提供了管理通信子网对象和把节点连接至它们的各种方 法。信道类同样可以由开发者以面向对象的方法自定义。一个信道实例可以模拟一条简单的线缆(wire),也可以模拟一个复杂的巨型以 太网交换机,甚至可以是无线网络中充满障碍物的三维空间。
本文中将使用几个信道模型的实例,包括:CsmaChannel、PointToPointChannel和WifiChannel。
CsmaChannel 信道模拟了一个可以用于实现载波侦听多路访问通信子网中的媒介,这个信道具有和以太网相似的功能。
PointToPointChannel 这个类代表一个简单的点对点信道,此通道上没有多点通信能力,可以有最多2个点至点连接的网络设备。在通道中有2种“线”。第一个连接的设备获取0号线上的传输,第二个设备得到1号线上的传输。每根线有空闲或传输状态。
Wi-FiChannel此无线通道实现描述为“又一网络模型”传播模型,在默认情况下,如果没有传播模型需要设置,则是调用者使用通道设置。
2.4、网络设备NetDevice
以前,如果想把一台计算机连接到网络上,你就必须买一根特定的网络线缆,并在你的计算机上安装称为外设卡的硬件设备。能够实现网络功能的外接卡被称为网络接口卡(网卡)。一张网卡如果缺少控制硬件的软件驱动是不能工作的。在 UNIX(或Linux)系统中,外围硬件被称为“设备”。设备通过驱动程序来控制,而网卡通过网卡驱动程序来控制。在UNIX和Linux系统中, 网卡被称为像eth0这样的名字。
在 ns-3中,网络设备这一抽象概念相当于硬件设备和软件驱动的总和。ns3仿真环境中,网络设备安装在节点上,使得节点通过信道和其他节点通信。像真实的计算机一样,一个节点可以通过多个网络设备同时连接到多条信道上。 网络设备由C++中的NetDevice类来描述。
NetDevice类提供了管理连接其他节点和信道对象的各种方法,并且允许开发者以面向对象的方法来自定义。本文中将使用几个特定的网络设备作为实例,它们分别是 CsmaNetDevice、PointToPointNetDevice 和 Wi-FiNetDevice。
正如以太网卡被设计成在以太网中工作一样,CsmaNetDevice被设计成在CSMA信道中工作,而PointToPointNetDevice在PointToPoint信道中工作,Wi-FiNetNevice在Wi-Fi信道中工作。
如果需要一个所有被创建的 NetDevice对象列表,就需要使用一个 NetDeviceContainer 对象来存放它们,就像本文使用一个NodeContainer对象来存放所创建节点。一个NetDeviceContainer被创建了。对于在NodeContainer 对象中的每一个节点,一个PointToPointNetDevice被创建和保存在NetDeviceContainer内。一个 PointToPointChannel 对象被创建,2 个 PointToPointNetDevices与之连接。
2.5、拓扑助手TopologyHelpers
在现实的网络中,你会发现主机己装有(或者内置)的网卡。在 ns-3中你会发现节点附加了网络设备。在大型仿真网络中,你需要在节点、网络设备和信道之间部署许多连接。
既然把网络设备连接到节点、信道、配置IP地址等在ns-3是很普遍的任务,那么干脆提供 “拓扑生成器”来使这个工作变得尽可能的容易。举例来说:创建一个网络设备,配置一个 MAC 地址,把此网络设备装载到节点上,设置节点的协议栈以及连接网络设备到一个信道,这些事情都需要许多分立的ns-3核心操作。而当需要把许多设备连接到多点信道,在网际间将单个网络进行连接时,则需要对ns-3核心进行更多的分立操作。本文提供了拓扑生成器来整合这些大量分立的步骤,使其成为一个简单易用的操作。
很明显,Helper 类可以极大地方便用户。例如 TopologyReaderHelper 类可以使得更容易配置和使用通用的 TopologyReader。再比如类 InternetStackHelper 是一个安装 PointToPointHelper 对象和点到点网络设备的网络协议栈的拓扑生成器类,它会为每一个节点容器中的节点安装一个网络协议栈(如TCP、UDP和IP等)
3、优化技术
在模拟过程中,数据跟踪与采集的方法有很多,大体上可以分为两大类:
一类是直接将执行过程显示在命令行中,这有助于调试仿真脚本,ns-3 使用的是Logging 系统;
另一类且更为常用的是将采集到的数据直接存放在一个文件中,以便后期的处理与分析,ns-3 使用的是 Tracing 系统。
本节将重点阐述使用 ns-3进行网络模拟时,如何记录模拟产生的数据,主要针对Logging系统进行讲述。
3.1、Logging系统
Logging系统提供7个等级,由低到高依次为:
LOG_ERROR—记录错误信息;
LOG_WARN—记录警告信息;
LOG_DEBUG—记录一些调试信息;
LOG_INFO—记录一些程序相夫的信息;
LOG_FUNCTION—当有函数被调用时,该调用信息就被记录;
LOG_LOGIC—对于整体逻辑的描述;
LOG_ALL—包含上述的所有信息。
每一个高等级记录的信息都包含低等级所要记录的信息。还有就是每一个LOG_TYPE 都等价于一个 LOG_LEVEL_TYPE , 比如说使用 LOG_FUNCTION和使用LOG_LEVEL_FUNTION的功能是一样的。
第一种方法是通过LogComponentEnable:
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
第二种方法是通过设置 NS_LOG 环境变量来提高日志记录级别并获取更多信息,而无需更改脚本并重新编译,如下所示:
export NS_LOG=UdpEchoClientApplication=level_all
这会将 shell 环境变量 NS_LOG 设置为字符串 UdpEchoClientApplication=level_all
我们可以通过在 NS_LOG 环境变量中输入以冒号分隔的组件列表来启用该组件。
export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func:UdpEchoServerApplication=level_all|prefix_func'
有时,能够查看生成日志消息的模拟时间也很有用。您可以通过 prefix_time:
export 'NS_LOG=UdpEchoClientApplication=level_all|prefix_func|prefix_time'
在这个模拟中,你也看不到很多幕后发生的事情。您可以通过打开系统中的所有日志记录组件来轻松跟踪整个过程。尝试将 NS_LOG 变量设置为以下值:
export 'NS_LOG=*=level_all|prefix_func|prefix_time'
上面的星号是日志记录组件通配符。这将打开模拟中使用的所有组件中的所有日志记录。
3.2、Tracing系统
Logging系统在调试脚本和分析网络运行的过程中固然起到了举足轻重的作用,但是,在有大量数据产生、收集和分析的脚本中Logging系统显得有些笨重繁琐。因此,ns-3提供了一个解诀上述问题的工具—Tracing系统。
Tracing系统包涵3个基本概念:Tracing Sources、Tracing Sink以及二者连接在一起的统一机制。
ns-3中提供了很多Helper类,这里类封装和隐藏了很多底层的机制,非常方便用户编写程序脚本,而不需要去理解复杂的细节。同样地,在 Tracing 模块中也提供了Helper类:AsciiTraceHelper。
3.2.1、ASCII Tracing
ASCII Tracing的含义就是一种以ASCII格式的信息输出。本文还 是以first.cc脚本为基本来学习如何使用AsciiTraceHelper生成ASCII 格式文档,首先,在脚本程序的Simulator::Run()代码前添加如下代码:
AsciiTraceHelper ascii;
pointToPoint.EnableAsciiAll (ascii.CreateFileStream ("myfirst.tr"));
第一行就不用过多解释了,就是声明一个 AsciiTraceHelper 的对象,为后面调用 AsciiTraceHelper 的成员函数做准备。第二行包含 2个函数,首先括号里面的函数 CreateFileStream 创建一个流文件并返回该文件作为外面函数 EnableAsciiAll 的参数,函数的细节不做过多的讲述,只要明白是创建一个名为myfirst.tr的文件。
而函数EnableAsciiAll的作用是通知helper将所有的关于pointto-point设备的仿真信息都打印成ASCII Tracing格式。
编译成功后,在文件夹ns-3.16中多出一个名为myfirst.tr的文件。打开这个文件,里面就包含本文要收集的信息,具体信息这里不再分析,后面会有一部分章节来讲述如何使用工具来分析这些标准格式的数据。
3.2.2、PCAP Tracing
helper 还可以生成以.pcap 为后缀的格式文件,这个文件可以使用Wireshark工具打开并分析,当然还有很多其他的工具也能做到这一点,针对这个本文不在这里进行讨论,主要给出如何让程序生成该类型文件,方法比生成ASCII Tracing文件还要简单,如下一句代码就可以:
pointToPoint.EnablePcapAll ("myfirst");
3.3、命令行
另一种无需编辑和构建即可更改 ns-3 脚本行为方式的方法是通过命令行参数。我们提供了一种机制来解析命令行参数,并根据这些参数自动设置局部和全局变量。
使用命令行参数系统的第一步是声明命令行解析器。如以下代码所示,
int
main(int argc, char *argv[])
{
...
CommandLine cmd;
cmd.Parse(argc, argv);
...
}
$ ./ns3 run "scratch/myfirst --PrintHelp"
myfirst [General Arguments]
General Arguments:
--PrintGlobals: Print the list of globals.
--PrintGroups: Print the list of groups.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintTypeIds: Print all TypeIds.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintVersion: Print the ns-3 version.
--PrintHelp: Print this help message.
您还可以将自己的 hook 添加到命令行系统。这可以通过对命令行解析器使用 AddValue 方法非常简单地完成。
让我们使用这个工具以完全不同的方式指定要回显的数据包数量。让我们将一个名为 nPackets 的局部变量添加到 main 函数中。我们将它初始化为 1 以匹配我们之前的默认行为。要允许命令行解析器更改此值,我们需要将该值挂接到解析器中。我们通过添加对 AddValue 的调用来实现此目的。继续更改 scratch/myfirst.cc 脚本,使其从以下代码开始,
int
main(int argc, char *argv[])
{
uint32_t nPackets = 1;
CommandLine cmd;
cmd.AddValue("nPackets", "Number of packets to echo", nPackets);
cmd.Parse(argc, argv);
...
向下滚动到脚本中我们设置 MaxPackets属性的位置,并对其进行更改,使其设置为变量 nPackets 而不是常量 1,如下所示。
echoClient.SetAttribute("MaxPackets", UintegerValue(nPackets));
现在,如果您运行脚本并提供 --PrintHelp 参数,您应该会看到新的 User Argument 列在帮助显示中。
$ ./ns3 run "scratch/myfirst --PrintHelp"
[Program Options] [General Arguments]
Program Options:
--nPackets: Number of packets to echo [1]
General Arguments:
--PrintGlobals: Print the list of globals.
--PrintGroups: Print the list of groups.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintTypeIds: Print all TypeIds.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintVersion: Print the ns-3 version.
--PrintHelp: Print this help message.
./ns3 run "scratch/myfirst --nPackets=2"