NS3入门

什么是NS-3?
离散事件驱动网络模拟器。看看官方的定义:(from http://www.nsnam.org/ )
ns-3 is a discrete-event network simulator for Internet systems, targeted primarily for research and educational use. ns-3 is free software, licensed under the GNU GPLv2 license, and is publicly available for research, development, and use.
ns-3 is intended as an eventual replacement for the popular ns-2 simulator. The project acronym “nsnam” derives historically from the concatenation of ns (network simulator) and nam (network animator).

NS-3 vs NS-2
NS-3虽然冠以一个“3”,但事实上跟它广泛流行的前任NS-2并非一脉相承,或者从使用角度上说,仅仅继承了一个名称而已。NS-3基本上是一个新的模拟器,不支持NS-2的API。NS-3是完全用C++编写的(也有可选的Python接口),而NS-2一部分模块使用C++而另一部分使用 OTcl。因而NS-3最大的特点就是脚本可以C++或Python语言,而在NS-2中,我们使用的是OTcl。
NS-3的功能仍旧在开发中,因此它远没有NS-2完善(当然NS-2的维护也在进行中)。NS-3并不包含目前所有NS-2的功能,但它具有某些新的特性:正确的多网卡处理、IP寻址策略的使用、更详细的802.11模块等等。
(出于最后的这句话,我们这次作业大胆地采用了NS-3进行仿真——此是后话。)

Latest stable release: ns-3.2.1 (November 20, 2008)

结构:
据说NS-3的架构看起来比NS-2清晰得多,从NS-3 Tutorial看起来确实是这样。NS-3中把网络构件分为四类:
·Node:终端节点,能够添加应用、协议、外部接口等。
·NetDevice:网卡及其驱动,有各种不同类型的网卡:CsmaNetDevice、PointToPointNetDevice、WifiNetDevice。
·Channel:通道,有各种不同类型的介质通道:CsmaChannel、PointToPointChannel、WifiChannel。
·Application:应用程序,包括UdpEchoClientApplication、UdpServerApplication等。
此外,NS-3中提供了一类称为Topology Helper的模块,对应每种拓扑连接有不同的Helper(例如CsmaNetHelper等),使用这些类来模拟现实中的安装网卡、连接、配置链路等过程,来简化工作。

NS-3对我来说也是一个小火星环境,因此也有许多火星文需要学习:
【名词解释】
POSIX :Portable Operating System Interface
一组操作系统API的协议/标准族,最开始为了Unix系统上的可移植性而开发的,也适用于其他操作系统。

Doxygen :Documentation Generator
支持C++、C、Java、Objective-C、Python、IDL、Fortran、VHDL、PHP、C#等各种语言的文档生成器,用于从源代码中生成说明文档。(类似于我之前使用过的Sandcastle,貌似更加强大些,有必要得学习一下。)

nam :Network Animator
基于Tcl/TK的网络动画演示工具,能提供拓扑和包级别的动画以及数据流观察。(参考http://www.isi.edu/nsnam/nam/ )

Mercurial
NS-3代码维护使用的源码版本控制管理系统

Waf
NS-3项目使用的新一代的基于Python的构建系统(Build System)

WireShark
一种GUI包嗅探器。由于NS-3能生成.pcap文件,因此可以使用类似于WireShark的软件对数据进行分析

tcpdump
另一种包嗅探器。在Linux下使用CLI进行数据分析

编译与运行

一、环境支持
如上文(NS-3入门[1]概念引入 )所述,编译/运行NS-3脚本需要保证Linux环境的设置(gcc、waf、tcpdump等),详细的必要软件包安装过程参见http://www.nsnam.org/wiki/index.php/Installation

二、NS-3C++脚本的编写
如前所述,NS-3的脚本使用C++语言(也支持python),使用四种类型的网络构件(Node、NetDevice、Channel、Application)。一个简单的脚本一般有以下步骤:
1、创建节点Node(使用类NodeContainer::Create()方法)
2、使用链路Helper类来帮助设置链路(包括PointToPointHelper、CsmaHelper、WifiHelper等类型)。 Helper类虽然不属于上述四类的网络构件,但它却极大地方便了拓扑的搭建,它可以帮助我们处理实际中诸如在两个终端安装网卡、连网线、Modern、配置上网方式、链路属性等底层工作,简化了仿真过程,使我们可以更专注于仿真的目的
3、安装IP协议栈(使用类InternetStackHelper::Install()方法)
4、设置IP地址(使用类Ipv4AddressHelper::SetBase()/Assign()方法)
5、在节点Node上安装应用程序(目前支持UdpServerServer、UdpEchoClient、PacketSink等)
6、设置仿真时间、启动仿真
===================================
一个简单的脚本(来自NS-3 Tutorial)及其解释
===================================
#include "ns3/core-module.h"
#include "ns3/simulator-module.h
#include "ns3/node-module.h"
#include "ns3/helper-module.h"
using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("Example");   //定义名称为“Example”的日志模块

int
main (int argc, char *argv[])
{
//以下两个语句启用UdpEcho应用程序的日志记录,其级别为LOG_LEVEL_INFO。关于NS-3的日志系统将在后续篇章进行介绍。
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);

NodeContainer nodes;   //1、创建两个节点
nodes.Create (2);  

PointToPointHelper pointToPoint;  //2、创建P2P类型的Helper
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));   //使用Helper设置链路属性
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

NetDeviceContainer devices;
devices = pointToPoint.Install (nodes);  //使用Helper将网卡安装到节点

InternetStackHelper stack;  //3、安装IP协议栈
stack.Install (nodes);

Ipv4AddressHelper address;  //4、分配IP地址
address.SetBase ("10.1.1.0", "255.255.255.0");
Ipv4InterfaceContainer interfaces = address.Assign (devices);  //分配到网卡


UdpEchoServerHelper echoServer (9);   //5.1、安装UdpServer应用服务,9表示服务端口
ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));
serverApps.Start (Seconds (1.0));
serverApps.Stop (Seconds (10.0));
serverApps.Start (Seconds (1.0));   //6.1、Server启动时间
serverApps.Stop (Seconds (10.0));

UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);   //5.2、安装UdpClient应用服务,需要指明服务器IP以及服务端口
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.)));
echoClient.SetAttribute ("acketSize", UintegerValue (1024));
ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));
clientApps.Start (Seconds (2.0));   //6.2、Client启动时间
clientApps.Stop (Seconds (10.0));

Simulator::Run ();   //6.3、启动仿真
Simulator:estroy ();
return 0;
}
===================================

三、编译与运行
当我们装好NS-3的运行环境之后,在NS-3的程序目录下会有一个scratch目录,其性质类似于VC/VC++环境下的Debug目录。
将上述脚本文件保存为example.cc,复制到scratch下面,然后在NS-3目录下使用命令waf完成编译,然后运行。例如:
$~/NS-3.2.1 > ./waf
$~/NS-3.2.1 > ./waf --run scratch/example
可以看到程序输出:
Entering directory ‘~/NS-3.2.1/build’
Compilation finished successfully
Sent 1024 bytes to 10.1.1.2
Received 1024 bytes from 10.1.1.1
Received 1024 bytes from 10.1.1.2

日志子系统:

NS-3日志子系统的提供了各种查看仿真结果的渠道:
一、使用Logging Module
0、【预备知识】日志级别及其对应的宏
NS-3提供了若干个日志级别来满足不同的Debug需求,每一级的日志内容都涵盖了低一级的内容。这些级别对应的宏从低到高排列为:
*NS_LOG_ERROR — Log error messages;
*NS_LOG_WARN — Log warning messages;
*NS_LOG_DEBUG — Log relatively rare, ad-hoc debugging messages;
*NS_LOG_INFO — Log informational messages about program progress;
*NS_LOG_FUNCTION — Log a message describing each function called;
*NS_LOG_LOGIC — Log messages describing logical flow within a function;
*NS_LOG_ALL — Log everything.
*NS_LOG_UNCOND — 无条件输出

方式1、通过设置shell环境变量NS_LOG使用日志系统
1.1)首先,定义好一个日志模块:
可以在脚本中使用宏NS_LOG_COMPONENT_DEFINE(name)定义一个日志模块。(注意,为了使用宏NS_LOG(name, level)来输出这个模块所定义的内容,这个定义语句必须写在每个脚本文件的开始。宏NS_LOG将在方式2中进行介绍。)
也有一些日志模块是内置的,比如上文的名为“UdpEchoClientApplication”“UdpEchoServerApplication”的模块就是UdpEcho应用程序内置的日志模块,只要使用了相应的类,就可以启用相应的日志模块。
1.2)在shell中通过设置环境变量NS_LOG,来控制仿真输出级别:
$~/ns-3.2.1 > export NS_LOG = '<日志模块名称> =level_all | prefix_func | prefix_time'
*level_all表示启用所有级别(=error | warn | debug | info | function | logic)
*prefix_func表示记录输出该消息的函数
*prefix_time表示加上时间前缀
$~/ns-3.2.1 > export NS_LOG = '<日志模块名称1>=level_all : <日志模块名称2>=info'
*符号:隔开两个不同的日志模块
$~/ns-3.2.1 > export NS_LOG = * = level_all
*符号*作为通配符。上行命令表示启用所有可用模块的所有日志级别。
*这一般会形成大量的数据,此时可以使用shell的输出重定向保存日志到文件里面:
$~/ns-3.2.1 > ./waf --run scratch/example >& log.out

方式2、通过在脚本里使用宏NS_LOG调用日志模块
2.0)宏NS_LOG(level, msg)用于定义对应level的输出内容;为了方便使用,系统预定义了各个级别的NS_LOG宏NS_LOG_ERROR等(参见【预备知识】):
#define NS_LOG_ERROR(msg)   NS_LOG(ns3:OG_ERROR, msg)
2.1)如上文,在脚本里使用宏NS_LOG_COMPONENT_DEFINE(name)定义一个日志模块;
2.2)使用宏LogComponentEnable(name, level)启用日志(对应地,有宏LogComponentDisable(name, level)用于禁用日志);
2.3)使用【预备知识】里定义的各种级别的宏输出内容,注意程序只会输出低于等于已经启用的level的宏内容。
NS_LOG_COMPONENT_DEFINE("Example");
LogComponentEnable("Example", LOG_LEVEL_INFO);   //等价于shell中:export NS_LOG = 'Example=info'
NS_LOG_WARN("Message:level_warn");
NS_LOG_INFO("Message:level_info");
NS_LOG_LOGIC("Message:level_logic");
//由于我们启用的日志level是INFO,因此编译运行后,程序会输出低于和等于INFO级别的内容,而高于INFO级别的宏内容不会被输出
//即,Message:level_warn和Message:level_info会被输出,而Message:level_logic不会被输出

===================================

二、使用Command Line参数
仿真一般是为了收集各种不同条件下的数据,常常需要改变一些变量。NS-3提供了Command Line参数接口,可以在运行时对脚本中的变量进行设置,免去了每次更改变量后要重新编译的麻烦。(相当于在运行前进行变量的scanf/cin操作,但因为有默认值,CLI更灵活一些。)
1、在脚本中添加语句
int main (int argc, char *argv[])
{
...
CommandLine cmd;
cmd.Parse (argc, argv);  
//将命令行输入的参数作为类CommandLine的参数进行分析
...
}
这样可以在shell中使用某些附加参数如PrintHelp:
$~/ns-3.2.1 > ./waf --run "scratch/example --PrintHelp"
这条命令将会列出example当前可用的命令参数:
Entering directory '/home/craigdo/repos/ns-3-dev/build'
Compilation finished successfully
--PrintHelp: Print this help message.
--PrintGroups: Print the list of groups.
--PrintTypeIds: Print all TypeIds.
--PrintGroup=[group]: Print all TypeIds of group.
--PrintAttributes=[typeid]: Print all attributes of typeid.
--PrintGlobals: Print the list of globals.
从输出中(倒数第二行)我们知道可以打印某些类的属性:
$~/ns-3.2.1 > ./waf --run "scratch/example --PrintAttributes=ns3:ointToPointNetDevice"
这条命令将会列出类型为PointToPointNetDevice的设备的属性:
--ns3:ointToPointNetDevice:ataRate=[32768bps]:
The default data rate for point to point links
知道了属性名称,我们也可以使用命令更改这个属性:
$~/ns-3.2.1 > ./waf --run "scratch/example --ns3:ointToPointNetDevice:ataRate=5Mbps"

2、使用CommandLine::AddValue添加自己的变量,使之成为CommandLine可以使用的参数
CommandLine cmd;
cmd.AddValue("nPackets", "Number of packets to echo", nPackets);   //(属性名称,属性说明,变量)
cmd.Parse(argc, argv);
这样在shell中我们可以在命令中更改这个属性:
$~/ns-3.2.1 > ./waf --run "scratch/example --nPackets=2"

===================================

三、使用Tracing System
1、启用ASCII Tracing
NS-3提供了类似NS-2的日志输出(*.tr文件),记录系统中的动作。在Simulator::Run()之前添加语句:
#include
...
std:fstream ascii;
ascii.open ("example.tr");
PointToPointHelper::EnableAsciiAll (ascii);
则运行后我们可以在example.tr文件中看到系统的日志(使用ASCII文本阅读器即可),其中每一行都是以+/-/d/r开头的:
+: An enqueue operation occurred on the device queue;
-: A dequeue operation occurred on the device queue;
d: A packet was dropped, typically because the queue was full;
r: A packet was received by the net device.
例如我们可以看到文件中的第一行(为了说明方便,这里分段编号显示),显示了一个入队操作:
00 +
01 2
02 /NodeList/0/DeviceList/0/$ns3:ointToPointNetDevice/TxQueue/Enqueue
03 ns3:ppHeader (
04  Point-to-Point Protocol: IP (0x0021))
05  ns3::Ipv4Header (
06   tos 0x0 ttl 64 id 0 offset 0 flags [none]
07   length: 1052 10.1.1.1 > 10.1.1.2)
08   ns3::UdpHeader (
09    length: 1032 49153 > 9)
10    Payload (size=1024)
其中编号为02的部分显示了发生操作的路径:根/NodeList是NS-3维护的所有节点列表,因此/NodeList/0表示编号为0的节点;随后的/DeviceList/0表示在该节点上的编号为0的NetDivece(比如网卡);接下来的$ns3::PointToPointNetDevice指明了该NetDivece的类型;最后的TxQueue/Enqueue表示在传送队列上发生了入队操作,也就是行开头的+所表现的意义。

2、启用PCAP Tracing
NS-3也可以生成*.pcap文件,从而可以使用诸如Wireshark、tcpdump(前文NS-3入门[1]概念引入 介绍过)等工具进行分析。
2.1)在脚本Simulator::Run()之前添加语句:
PointToPointHelper::EnablePcapAll ("example");
这个语句将会产生若干*.pcap文件,命名为example--.pcap,分别记录每个设备的日志。也可以使用语句***Helper::EnablePcap (filename, NodeId, DeviceId)来只产生特定设备的pcap文件:
PointToPointHelper::EnablePcap ("example", p2pNodes.Get (0)->GetId (), 0);  //只产生example-0-0.pcap文件
2.2)使用tcpdump在命令行阅读pcap文件:
~/ns-3.2.1 > tcpdump -r example-0-0.pcap -nn -tt
reading from file second-0-0.pcap, link-type PPP (PPP)
2.000000 IP 10.1.1.1.49153 > 10.1.2.4.9: UDP, length 1024
2.007382 IP 10.1.2.4.9 > 10.1.1.1.49153: UDP, length 1024
2.3)使用Wireshark等软件打开pcap文件。

你可能感兴趣的:(NS3入门)