NS3基本介绍及入门

转自http://www.osedu.net/yuanchuang-article/ns2/2011-07-23/275.html

http://hi.baidu.com/mucenl/blog/item/afdf671f9aa803e5e0fe0ba7.html

什么是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)及其解释
=========================================================================
/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */       //指示Emacs工作在C++模式下

#include "ns3/core-module.h"      //模块已分为大类(各包含各模块的头文件)
#include "ns3/simulator-module.h"    //位于%build derectory%/ns3/xx.h,%build derectory%一般为build/debug或build/optimized
#include "ns3/node-module.h"
#include "ns3/helper-module.h"

using namespace ns3;

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");         //使用NS_LOG宏定义component
//log modules core/debugging/logging

int 
main (int argc, char *argv[])
{
//LOG使仿真期间分组收发时会显示仿真信息
//开/关命令行消息日志(允许程序员将消息显示在屏幕上),默认关闭
LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);       //开启两个类的Log组件,并设置log级别为LOG_LEVEL_INFO
LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);       //同上

/**********************网络拓扑部分*********************/
//NodeContainer为类名,用于创建、管理、访问node对象
NodeContainer nodes;   //创建NodeContainer类的对象
nodes.Create (2);

//PointToPointHelper类,创建、配置、安装NetDevice对象(object)
PointToPointHelper pointToPoint;
//设置PointToPointHelper类对象的Channel和NetDevice参数
pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps")); //数据率=5Mbps
pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));    //发送延迟=2ms

NetDeviceContainer devices;        //device对象用来存储返回的网络设备列表
//PointToPointHelper类对象的Install方法将NodeContainer对象作参数,
//该方法使用先前设置的参数(SetChannelAttribute)创建P2PChannel;
//并对输入参数的每个节点,创建一个使用先前参数(SetDeviceAttribute)的P2PNetDevice;
//并将节点、信道、设备三者关联!
devices = pointToPoint.Install (nodes); //该句完成信道、设备配置和安装!!!
//Install返回值是网络设备列表,以供下文使用

InternetStackHelper stack;
stack.Install (nodes); //安装协议栈(TCP/UDP/IP等)到NodeContainer类对象的每个node

Ipv4AddressHelper address;
//address对象的SetBase方法:参数1为子网地址基,参数2为子网掩码
//给10.1.1.0子网分配IP地址,默认IP地址从1开始!顺序增加!
//即若有2个node,node1IP为10.1.1.1,node2IP为10.1.1.2
address.SetBase ("10.1.1.0", "255.255.255.0");

//interfaces容器中第0个接口与nodes容器中第0个节点匹配
//interfaces容器中第1个接口与nodes容器中第1个节点匹配
//IP安装(Assign方法),分配接口<-->地址
//接口0/1分别对应地址10.1.1.1/2!!!!
Ipv4InterfaceContainer interfaces = address.Assign (devices); //Assign方法用devices作参数,指定IP到设备
//返回值是Ipv4InterfaceContainer类对象列表,同前NetDeviceContianer列表

/********************网络拓扑部分结束*********************/

/**********************应用程序部分*********************/
//创建Server程序,等待UDP分组,并将其发回给sender
UdpEchoServerHelper echoServer (9); //9:Server等待分组到来的端口
//将端口作为构造函数的一个参数,也可使用SetAttribute设置端口

ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));   //安装UdpEchoServer Application,返回Ptr<Node>
//返回Application列表,用ApplicationContainer类存储            //Server用节点1(地址xxx.2)
serverApps.Start (Seconds (1.0)); //Application的启动/停止(ApplicationContainer类的方法)
serverApps.Stop (Seconds (10.0)); //Seconds方法转化得到NS3 Time


//创建Client程序
UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9); //UdpEchoClientHelper构造函数中设置前两个属性
//参数1为Remote Address,参数填写的地址索引1,地址为xxx.2
//参数2为Remote Port,
echoClient.SetAttribute ("MaxPackets", UintegerValue (1));    //允许的最大分组的数目,此处指示仅发送1个分组
echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.))); //分组间等待的间隔
echoClient.SetAttribute ("PacketSize", UintegerValue (1024));   //单位为Byte

ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));   //Client用节点0(地址xxx.1)
//返回Application列表,用ApplicationContainer类存储
clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (10.0));
/********************应用程序部分结束*********************/

/**********************仿真控制部分*********************/
Simulator::Run ();
Simulator::Destroy (); //清除已创建的对象等
/*******************仿真控制部分结束*********************/

return 0;
}


//程序运行后的显示结果
/*
[ 518/1087] cxx: scratch/myfirst.cc -> build/debug/scratch/myfirst_2.o
[1087/1087] cxx_link: build/debug/scratch/myfirst_2.o -> build/debug/scratch/myfirst
Waf: Leaving directory `/home/shawnlee/tarballs/ns-allinone-3.9/ns-3.9/build'
'build' finished successfully (6.366s)
Sent 1024 bytes to 10.1.1.2             //由UdpEchoClientApplication输出的log消息
Received 1024 bytes from 10.1.1.1       //由UdpEchoServerApplication输出的log消息
Received 1024 bytes from 10.1.1.2
*/


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

三、编译与运行
当我们装好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文件。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值