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文件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章