NS3基本知識

轉載自http://blog.sina.com.cn/s/blog_61e2420a0101jy5j.html

3 NS3快速入門

本章節通過閱讀分析一個例子程序(first.cc)的源代碼,並通過運行該例子程序,快速理解ns3中的幾個概念。

3.1 NS3中的幾個關鍵概念

3.1.1 節點Node

在網絡術語中,任何一臺連接到網絡的計算設備被稱爲主機,亦稱爲終端。NS3是一個網絡模擬器,而非一個專門的因特網模擬器,爲此我們避開術語“主機”,因爲這個詞太容易讓人聯想到因特網和及其相關協議。因此,我們選用了一個來源於圖論,在其他網絡模擬器中亦廣泛使用的術語:節點。

NS3中基本計算設備被抽象爲節點。節點由用C++編寫的Node類來描述。Node類提供了用於管理計算設備的各種方法。

可以將節點設想爲一臺可以添加各種功能的計算機。爲了使一臺計算機有效地工作,我們可以給它添加應用程序,協議棧,外設卡及驅動程序等。NS3採用了與此相同的模型。

3.1.2 信道

在現實世界中,人們可以把計算機連接到網絡上。通常我們把網絡中數據流流過的媒介稱爲信道。當你把以太網線插入到牆壁上的插孔時,你正通過信道將計算機與以太網連接。在NS3中,可以把節點連接到代表數據交換信道的對象上。在這裏,基本的通信子網這一抽象概念被稱爲信道,用C++編寫的Channel類來描述。

Channel類提供了管理通信子網對象和把節點連接至信道的各種方法。信道類同樣可以由開發者以面向對象的方法自定義。一個信道實例可以模擬一條簡單的線纜(wire),也可以模擬一個複雜的巨型以太網交換機,甚至無線網絡中充滿障礙物的三維空間。

在本章中我們將使用幾個信道模型的實例,包括:CsmaChannel, PointToPointChannel和WifiChannel。舉例來說,CsmaChannel信道模擬了用於一個可以實現載波偵聽多路訪問的信道,這個信道具有和以太網相似的功能。

3.1.3 網絡設備

如果想把一臺計算機連接到網絡上,必須在計算機上安裝有網卡。一張網卡如果缺少控制硬件的軟件驅動是不能工作的。在Unix/Linux系統中,外圍硬件被劃爲“設備”。設備通過驅動程序來控制,而網卡通過網卡驅動程序來控制。在Unix/Linux系統中,網卡被稱爲像eth0這樣的名字。

在NS3中,網絡設備這一抽象概念相當於硬件設備和軟件驅動的總和。NS3仿真環境中,網絡設備相當於安裝在節點上,使得節點通過信道和其他節點通信。像真實的計算機一樣,一個節點可以通過多個網絡設備同時連接到多條信道上。

網絡設備由用C++編寫的NetDevice類來描述。NetDevice類提供了管理連接其他節點和信道對象的各種方法,並且允許開發者以面向對象的方法來自定義。我們在本教程中將使用幾個特定的網絡設備的實例,它們分別是CsmaNetDevice, PointToPointNetDevice, 和 WifiNetDevice。正如以太網卡被設計成在以太網中工作一樣,CsmaNetDevice被設計成在csma信道中工作,而PointToPointNetDevice 在PointToPoint信道中工作,WifiNetNevice在wifi信道中工作。

3.1.4 應用程序

計算機軟件通常可分爲兩大類:系統軟件和應用軟件。系統軟件根據計算模型配置,並管理計算機中的各種資源,如內存,處理器週期,硬盤,網絡等。系統軟件通常並不直接使用這些資源來完成用戶任務。用戶往往需要運行應用程序來完成一些特定的任務,而應用程序需要使用由系統軟件控制的資源。

通常,系統軟件和應用軟件的界線表現爲特權級別的變化,而這種變化是通過操作系統的自陷功能(operating system traps)來實現的。在NS3中並沒有真正的操作系統的概念,更沒有特權級別或者系統調用的概念。然而,我們有應用程序的概念。正如“現實世界”中在計算機上運行應用程序以執行各種任務一樣,NS3仿真環境中的應用程序在節點上運行來驅動模擬過程。

在NS3中,需要被仿真的用戶程序被抽象爲應用。用Application類來描述。這個類提供了管理仿真過程中用戶層應用的各種方法。開發者應當用面向對象的方法自定義和創建新的應用。在本教程中,我們會使用Application類的兩個實例:UdpEchoClientApplication 和UdpEchoServerApplication。這些應用程序包含了一個client應用和一個server應用來發送和迴應仿真網絡中的數據包。

3.2 分析例子程序first.cc的源代碼

進入ns-3.15/examples/tutorial目錄。你會發現一個叫first.cc的文件。這一個腳本會在兩個節點間創建一個簡單的點到點的連接,並且在這兩個節點之間傳送一個數據包。爲方便後續分許,先將first.cc的源代碼粘貼如下:

#include "ns3/core-module.h"

#include "ns3/network-module.h"

#include "ns3/internet-module.h"

#include "ns3/point-to-point-module.h"

#include "ns3/applications-module.h"

 

using namespace ns3;

 

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

 

intmain (int argc, char *argv[])

{

LogComponentEnable ("UdpEchoClientApplication", LOG_LEVEL_INFO);

LogComponentEnable ("UdpEchoServerApplication", LOG_LEVEL_INFO);

 

NodeContainer nodes;

nodes.Create (2);

 

PointToPointHelper pointToPoint;

pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));

pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

 

NetDeviceContainer devices;

devices = pointToPoint.Install (nodes);

 

InternetStackHelper stack;

stack.Install (nodes);

 

Ipv4AddressHelper address;

address.SetBase ("10.1.1.0", "255.255.255.0");

 

Ipv4InterfaceContainer interfaces = address.Assign (devices);

 

UdpEchoServerHelper echoServer (9);

 

ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));

serverApps.Start (Seconds (1.0));

serverApps.Stop (Seconds (10.0));

 

UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);

echoClient.SetAttribute ("MaxPackets", UintegerValue (1));

echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.0)));

echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

 

ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));

clientApps.Start (Seconds (2.0));

clientApps.Stop (Seconds (10.0));

 

Simulator::Run ();

Simulator::Destroy ();

return 0;

}

3.2.1 模塊包含

代碼一般是以一系列的include聲明開始的:

#include "ns3/core-module.h"

#include "ns3/simulator-module.h"

#include "ns3/node-module.h"

#include "ns3/helper-module.h"

爲了幫助高層的腳本用戶處理大量的系統中的include文件,我們把所有的包含文件,根據模塊功能,進行了大致的分類。我們提供了一個單獨的include文件,這個文件會遞歸加載所有會在每個模塊中會被使用的include文件。NS3提供了按大致功能分類的一組include文件,在使用時只需選擇包含這幾個包含文件(include文件),而不用考慮複雜的依賴關係,省去在尋找所需要的頭文件上花費的不必要的時間。這不是最有效地方法但很明顯讓編寫腳本文件容易多了。

在編譯的過程中,每一個ns-3的include文件被放在build目錄下一個叫ns3的目錄中,這樣做可以避免include文件名的衝突。ns3/core-module.h與src/core目錄下的模塊相對應。查看ns3目錄會發現大量的頭文件。當你編譯時,Waf會根據配置把在ns3目錄下的公共的頭文件放到build/debug 或者build/optimized目錄下。Waf也會自動產生一個模塊include文件來加載所有的公共頭文件。

當然,如果遵循着這個手冊走的話,你可能已經使用過如下命令:

./waf -d debug --enable-examples --enable-tests configure

來配置工程以完成調試工作。你可能同樣使用瞭如下命令:

./waf

來編譯ns-3。現在如果你進入../../build/debug/ns3 目錄的話你會發現本節開頭提到的四個頭文件。仔細看一下這些文件的內容,會發現它們包含了相關模塊中的所有的include文件。

3.2.2 命名空間

在first.cc腳本的下一行是namespace的聲明。

using namespace ns3;

NS3工程是在一個叫做ns3的C++ 命名空間中實現的。這把所有與ns3相關的聲明,集中在一個與全局命名空間相區別的命名空間中。我們希望這樣會給ns3與其他代碼的集成帶來好處。C++用“using”語句用來把ns-3 namespace引入到當前的(全局的)作用域中。這個聲明就是說,你不用爲了使用ns-3的代碼而必須在所有的ns-3代碼前打上ns3:: 作用域操作符。如果對命名空間並不熟悉,可以查閱任何的C++手冊並比較ns3命名空間和標準”std”命名空間的使用。

3.2.3 日誌

下一句腳本如下:

NS_LOG_COMPONENT_DEFINE ("FirstScriptExample");

這一行聲明瞭一個叫FirstScriptExample的日誌組件,通過引用FirstScriptExample這個名字的操作,可以實現打開或者關閉控制檯日誌的輸出。

3.2.4 Main函數

下面的腳本是:

int

main (int argc, char *argv[])

{

這就是你的腳本程序的主函數的聲明。正如任何其它C++程序一樣,你需要定義一個會被第一個執行的主函數。你的ns-3腳本沒有什麼特別的,就和一個普通的C++程序一樣。

再接下來兩行腳本是用來使兩個日誌組件生效的。它們被內建在Echo Client 和Echo Server 應用中:

LogComponentEnable("UdpEchoClientApplication", LOG_LEVEL_INFO);

LogComponentEnable("UdpEchoServerApplication", LOG_LEVEL_INFO);

3.2.5 使用基本對象模型搭建仿真拓撲

3.2.5.1使用NodeContainer類

在first.cc腳本中的下面兩行將會創建ns-3節點對象。

NodeContainer nodes;

nodes.Create (2);

上面的第一行只是聲明瞭一個名爲”nodes”的NodeContainer。第二行調用了nodes對象的Create()方法創建了兩個節點。

3.2.5.2使用PointToPointHelper類

接下來,我們將會用到被稱爲拓撲輔助工具的helper類。這些helper類裏面封裝了低級的方法,有助於我們高效的建立仿真拓撲。

回憶一下我們的兩個關鍵抽象概念:網絡設備、信道。在真實的世界中,這些東西大致相當於網卡和網線。需要說明的是這兩樣東西緊密的聯繫在一起而不能夠把它們交互地使用(比如以太網設備和無線信道就不能一起使用)。在這個腳本中使用了PointToPointHelper來配置和連接網絡設備PointToPointNetDevice和信道PointToPointChannel對象。

在腳本中下面的三句話是:

PointToPointHelper pointToPoint;

pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));

pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

其中第一行,

PointToPointHelper pointToPoint;

在棧中初始化了一個PointToPointHelper的對象pointToPoint。而緊接着的下一行,

pointToPoint.SetDeviceAttribute ("DataRate", StringValue ("5Mbps"));

從上層的角度告訴PointToPointHelper對象當創建一個PointToPointNetDevice對象時使用“5Mbps"來作爲數據速率。

從細節方面講,字符串“DataRate”與PointToPointNetDevice的一個屬性相對應。如果查看ns3::PointToPointNetDevice 類,並閱讀GetTypeId 方法的文檔,你會發現設備定義了一系列屬性,在這些屬性中就有“DataRate”。大部分用戶可見的ns-3對象都有類似的屬性列表。正如你在下面的部分會看到的一樣,我們使用了這個機制以方便地配置仿真器,而不用重新對源代碼進行編譯。

與PointToPointNetDevice上的“DataRate”類似,PointToPointChannel也有一個Delay屬性:

pointToPoint.SetChannelAttribute ("Delay", StringValue ("2ms"));

告訴PointToPointHelper使用"2ms"(2毫秒)作爲每一個被創建的點到點信道傳輸延時值。

3.2.5.3使用NetDeviceContainer類

現在我們有一個包含兩個節點的NodeContainer對象。我們有一個準備在兩個節點之間創建PointToPointNetDevices和wirePointToPointChannel對象的PointToPointHelper對象。正如我們使用NodeContainer對象來爲我們創建節點,我們會讓pointToPointHelper來做關於創建,配置和安裝設備的工作。

我們使用一個NetDeviceContainer對象來存放需要所有被創建的NetDevice對象,就像我們使用一個NodeContainer對象來存放我們所創建節點。下面兩行代碼:

NetDeviceContainer devices;

devices = pointToPoint.Install (nodes);

會完成設備和信道的配置。第一行聲明瞭上面提到的設備容器,第二行完成了主要工作。PointToPointHelper的Install()方法以一個NodeContainer對象作爲一個參數。在Install()方法內,一個NetDeviceContainer被創建了。對於在NodeContainer 對象中的每一個節點(對於一個點到點鏈路必須明確有兩個節點),都將有一個PointToPointNetDevice被創建和保存在設備容器內,有一個PointToPointChannel對象被創建,兩個PointToPointNetDevices與之連接。當PointToPointHelper對象創建時,那些在helper中被預先設置的屬性被用來初始化對象對應的屬性值。

當調用了pointToPoint.Install(nodes)後,我們會有兩個節點,每一個節點安裝了點到點網絡設備,在它們之間是一個點到點信道。兩個設備會被配置在一個有2ms傳輸延時的信道上以5Mbps的速率傳輸數據。

3.2.5.4使用InternetStackHelper類

我們現在已經配置了節點和設備,但是我們還沒有在節點上安裝任何協議棧。下面兩行代碼完成這個任務:

InternetStackHelper stack;

stack.Install (nodes);

類InternetStackHelper 是一個輔助安裝網絡協議棧的helper類。其中Install()方法以NodeContainer 對象作爲參數,當它被執行後,它會爲節點容器中的每一個節點安裝一個網絡協議棧(TCP,UDP,IP等)。

3.2.5.5使用Ipv4AddressHelper類

下面我們需要爲節點上的設備設置IP地址。我們也提供了一個helper類來管理IP地址的分配。當執行實際的地址分配時唯一用戶可見的API是設置IP地址和子網掩碼。

在我們的範例腳本文件first.cc的下兩行代碼

Ipv4AddressHelper address;

address.SetBase ("10.1.1.0", "255.255.255.0");

聲明瞭一個helper對象,並且告訴它應該開始從10.1.1.0開始以子網掩碼爲255.255.255.0分配地址。地址分配默認是從1開始並單調的增長,所以在這個基礎上第一個分配的地址會是10.1.1.1,緊跟着是10.1.1.2等等。底層NS3系統事實上會記住所有分配的IP地址,如果你無意導致了相同IP地址的產生,這將是一個致命的錯誤(順便說一下,這是個很難調試正確的錯誤)。

下面一行代碼,

Ipv4InterfaceContainer interfaces = address.Assign (devices);

完成了真正的地址配置。在NS3中我們使用 Ipv4Interface對象將一個IP地址同一個網絡設備關聯起來。正如我們有時候需要一個網絡設備列表一樣,我們有時候需要一個 Ipv4Interface對象的列表。Ipv4InterfaceContainer提供了這樣的功能。

現在我們有了一個安裝了協議棧,配置了IP地址的點到點的網絡。剩下的所要做的事情是運用它來產生數據通信。

3.2.5.6 Applications類

NS3系統的另一個核心抽象是Application類。在這個腳本中我們用兩個特定的Application類:UdpEchoServerApplication和UdpEchoClientApplication。正如我們先前聲明過的一樣,我們使用helper對象來幫助配置和管理潛在的對象。在這裏,我們用UdpEchoServerHelper 和UdpEchoClientHelper對象來使我們的工作更加容易點。

首先來看UdpEchoServerHelper類:

下面的代碼用來在我們之前創建的節點上設置一個UDP 回顯服務應用。

UdpEchoServerHelper echoServer (9);

ApplicationContainer serverApps = echoServer.Install (nodes.Get (1));

serverApps.Start (Seconds (1.0));

serverApps.Stop (Seconds (10.0));

上面一片代碼中的第一行聲明瞭UdpEchoServerHelper。像往常一樣,這個並非應用本身,這是一個用來幫助創建真正應用的helper對象。我們約定在helper類的對象中放置必需的屬性。本例中,除非我們告知helper對象服務器和客戶端所共知的一個端口號,否則這個helper對象是不會起任何作用的。

同其它helper對象類似,UdpEchoServerHelper對象有一個Install()方法。實際上是這個方法的執行,才初始化回顯服務器的應用,並將應用連接到一個節點上去。有趣的是,install()方法把NodeContainter參數,正如我們看到的其他安裝方法一樣。這裏有一個C++隱式轉換,此轉換以nodes.Get(1)的結果作爲輸入,並把它作爲一個NodeContainer的構造函數的參數,最終這個新構造的NodeContainer被送入Install方法中去。

我們現在會看到echoServer.Install將會在管理節點的NodeContainer容器索引號爲1的機節點上安裝一個UdpEchoServerApplication。安裝會返回一個容器,這個容器中包含了指向所有被helper對象創建的應用指針。

應用程序對象需要一個時間參數來“開始”產生數據通信並且可能在一個可選的時間點“停止”。我們提供了開始和停止的兩個參數。這些時間點是用ApplicationContainer的方法Start和Stop來設置的。這些方法以”Time”對象爲參數。本例中,我們傳遞了double類型對象1.0到Seconds的一個方法,通過seconds()方法,把它轉換到ns-3的Time對象。需要注意的是,這裏的轉換規則是模型的作者所控制的,並且C++也有它自己的標準,所以你不能總是假定參數會按照你的意願順利地轉換。下面兩行,

serverApps.Start (Seconds (1.0));

serverApps.Stop (Seconds (10.0));

會使echo服務應用在1s時開始(生效)並在10s時停止(失效)。既然我們已經聲明瞭一個模擬事件(就是應用的停止事件)在10s時被執行,模擬至少會持續10s。

然後再來看UdpEchoClientHelper類:

echo客戶端應用的設置與回顯服務器端類似。也有一個UdpEchoClientHelper來管理UdpEchoClientApplication。

UdpEchoClientHelper echoClient (interfaces.GetAddress (1), 9);

echoClient.SetAttribute ("MaxPackets", UintegerValue (1));

echoClient.SetAttribute ("Interval", TimeValue (Seconds (1.)));

echoClient.SetAttribute ("PacketSize", UintegerValue (1024));

ApplicationContainer clientApps = echoClient.Install (nodes.Get (0));

clientApps.Start (Seconds (2.0));

clientApps.Stop (Seconds (10.0));

然而,對於echo客戶端,我們需要設置五個不同的屬性。首先兩個屬性是在UdpEchoClientHelper的構建過程中被設置的。按照UdpEchoClientHelper構造函數的格式,我們傳遞了”RemoteAdress”和”RemotePort”屬性,來實例化對象。

在上面的第一行代碼中,我們創建了一個UdpEchoClientHelper的對象,並告訴它設置客戶端的遠端地址爲服務器節點的IP地址。我們同樣告訴它準備發送數據包到端口9。

“MaxPackets”屬性告訴客戶端我們所允許它在模擬期間所能發送的最大數據包個數。

“Interval”屬性告訴客戶端在兩個數據包之間要等待多長時間。

“PacketSize”屬性告訴客戶端它的數據包應該承載多少數據。本例中,我們讓客戶端發送一個1024字節的數據包。

正如echo服務端一樣,我們告訴echo客戶端何時來開始和停止,這裏我們使客戶端在模擬器中時間爲2s的時候開始(即服務端生效1s後纔開始)。

3.2.5.7Simulator類

下面我們所需要做的就是運行模擬器,這是用全局函數Simulator::Run.來做到的:

Simulator::Run ();

當我們調用瞭如下方法時:

serverApps.Start (Seconds (1.0));

serverApps.Stop (Seconds (10.0));

...

clientApps.Start (Seconds (2.0));

clientApps.Stop (Seconds (10.0));

實際上我們是在模擬器中1.0s,2.0s,和10.0s時預設了時間的發生。當Simulator::Run被調用時,系統會開始遍歷預設事件的列表並執行。首先它會在1.0s時運行事件,這個事件會使echo服務端應用生效(這個事件會預設更多的其他事件)。接下來仿真器會運行在t=2.0s時的事件,即讓echo客戶端應用開始。同樣的,這個事件可能會預定更多的其他事件。在echo客戶端應用中的開始事件的執行會通過給服務端傳送一個數據包來開始仿真的數據傳送階段。

發送一個數據包給服務端會引發一系列更多的事件。這些事件會被預設在此事件之後,並根據我們已經在腳本中設定的時間參數來執行數據包的應答。

其實,我們只發送了一個數據包(回憶一MaxPackets屬性被設置爲1),在此之後,那個被單獨的客戶端應答請求所引發的連鎖反應會停止,並且模擬器會進入空閒狀態。當這發生時,生下來的事件就是服務端和客戶端的Stop事件。當這些事件被執行後,就沒有將來的事件來執行了,函數Simulator::Run會返回。整個模擬過程就結束了。

下面剩下的事情就是清理了。這個通過調用全局函數Simulator::Destroy來完成。當該方法被執行後,模擬器中所有創建的對象將被銷燬。你自己並不需要追蹤任何對象,你所需要做的僅僅是調用Simulator::Destroy並且退出。ns-3系統會幫你料理這些繁雜的任務。在first.cc腳本中對應的代碼如下:

Simulator::Destroy ();

return 0;

}

3.3 運行first.cc腳本程序

要運行自己的腳本,你所需要做的僅僅是把你的腳本放到scratch目錄下,並運行waf,這樣你的腳本就會被編譯。

接下來我們來運行上面分析過的first.cc腳本。我們先回到高層目錄後複製examples/tutorial/first.cc文件到scratch目錄下,並改名爲myfirst.cc文件。

cp examples/tutorial/first.cc scratch/myfirst.cc

現在使用waf命令來編譯自己的第一個實例腳本:

./waf

你應該可以看到消息報告說你的myfirst範例被成功編譯了。

Waf: Entering directory `/home/…/build'

………

Waf: Leaving directory `/home/…/build'

'build' finished successfully (2.357s)

現在你能夠運行這個例子(注意如果你在scratch目錄編譯了你的程序,你必須在scratch目錄外運行它):

./waf --run scratch/myfirst

注意這裏是myfirst而不是myfirst.cc!

你應該能看到一些輸出:

Waf: Entering directory `/home/…/build'

Waf: Leaving directory `/home/…/build'

'build' finished successfully (0.418s)

Sent 1024 bytes to 10.1.1.2

Received 1024 bytes from 10.1.1.1

Received 1024 bytes from 10.1.1.2

這裏可以看到編譯系統先檢查文件被編譯了,接着運行了它。你看到在echo日誌構件顯示了它已經發送了1024字節到在10.1.1.2的echo服務端。還可以看到回顯服務器端的日誌構件顯示他從10.1.1.1接收到了1024字節。接下來echo服務端應答了數據包,你能看到echo客戶端記錄了它已經接收到了從服務端發送過來的回顯數據包。

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