什么是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 <fstream> ... 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-<Node编号>-<NetDevice编号>.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文件。 |