Ns2 一個常用的網路模擬器 台灣科技大學資管系資料庫實驗室 洪振洲
Ns2 一個常用的網路模擬器 基本介紹與安裝
認識NS2 The NS-2 is a powerful tool for network simulation. There are many researchers use ns-2 to validate their algorithms and protocols for their network researches. NS-2 provides many supports for simulation of TCP, routing, and multicast protocol over wired and wireless network. It also provide topology generator to create topology from a number of nodes to thousand of nodes.
NS2 資源 官方網站 中文網頁 http://www.isi.edu/nsnam/ns/ 下載NS2 source, 最完整NS2手冊 http://netlab.cse.yzu.edu.tw/ns2/ 詳細Windows安裝 資源整理 http://140.116.72.80/~smallko/ns2/ns2.htm NS2 詳細的資源
安裝需求 作業系統: Linux Windows + cygwin (在windows 下模擬的Linux 環境) 檔案空間需求 約 1GB
下載NS2 由official site 下載 all-in-one 的版本 目前最新版本 2.29 由於Ns2 為網路社群共享,所以有各式各樣的修改版本. 我使用的是 2.1b8: 是為了實現AdHoc下面的Multi-cast 所改編的版本.
簡要安裝步驟 (I) Linux 下的安裝(可以參考 install_on_Suse.txt) 先create 安裝目錄,並給予適當權限 以下假設安裝在 /opt/ns2 必先安裝4個重要的library Tcl 8.3.2 Tk 8.3.2 Otcl 1.0a7 Tclcl 1.0b11
簡要安裝步驟 (II) 將library copy 到 /opt/ns2 之下 利用指令 “tar –zxvf 檔名” 解開 進入每一個子目錄中,依序執行 ./configure 與 make 兩個指令. 將ns2 的主檔copy 到 /opt/ns2之下, 並用 “tar –zxvf 檔名” 的指令解開 進入解開後的子目錄,打入 ./configure 的指令 先別執行 make, 我們要修改Makefile
簡要安裝步驟 (III) 利用文字編輯器修改ns2 解開後目錄中的 “Makefile”, 找到一行叫 “all :$(NS) all-recursive”把其中的 all-recursive 拿掉 執行 make 如果沒有錯誤訊息,就是安裝成功了
Ns2 一個常用的網路模擬器 Overview of NS2
What should we do?
C++ 與 TCL NS2 中的元件是由C++ 撰寫而成 劇本檔語言是 TCL 對於研究新protocol 的研究者,著重於如何用C++撰寫元件(agent) 對於研究複雜系統效能者,著重於如何用TCL描述系統
Use C++ to write your first NS2 agent.
AgentEcho 簡單的模擬 adhoc network 下面的echo 功能. 我們預計在”AdhocEcho”中實現的功能 所有echo 的邏輯都將寫在 名為”AdhocEcho” 的Agent 之中 我們預計在”AdhocEcho”中實現的功能 創造並列印節點基本資料 可以送出/回應 自訂的echo packet 利用時鐘來建立"Time-out” 機制
在開始之前 建立專案子目錄: 步驟一覽 在NS2 主目錄之下的“AdHocEcho”子目錄 STEP 1. 建立 C++ 中AdHocEcho所需的 Head STEP 2. 建立 AdhocEcho Agent 所需的基本架構 STEP 3. 建立 Tcl srcipt來測試初步結果. STEP 4. 修改C++ 檔案,使有 request/response 的能力 STEP 5. 修改C++ 檔案,使其有偵測Time-out 的能力
Echo Protocol 簡述 Type = 0 (Request) FromNode A FromTime T ReplyNode 若A 在 T 時間 執行Echo 封包內容: Type = 0 (Request) FromNode A FromTime T ReplyNode ??? ReplyTime A
Echo Protocol 簡述 Type = 1 (Reply) FromNode A FromTime T ReplyNode B 若B 在 T2 時間 收到來自A 的Echo_request B B 將只回應 A 封包內容: A Type = 1 (Reply) FromNode A FromTime T ReplyNode B ReplyTime T2
Step 1-Step 3 建立最簡單的echo agent,並加以測試 Ns2 一個常用的網路模擬器 Step 1-Step 3 建立最簡單的echo agent,並加以測試
STEP 1. 建立 C++ 中Agent所需的 Head AdhocEcho.h (I) // 講義第二頁,程式一 自訂的packet 表頭 struct hdr_ahe { // 表頭結構的定義 int FromNode; double FromTime; int ReplyNode; double ReplyTime; int Type; // Type=0: request, Type=1: reply int size_; int& size() { return size_; } static int offset_; inline static int& offset() { return offset_; } inline static hdr_ahe* access(const Packet* p) { return (hdr_ahe*) p->access(offset_); } }; 自訂表頭
STEP 1. 建立 C++ 中Agent所需的 Head AdhocEcho.h (II) // 講義第二頁,程式二 自訂的agent class 的定義 class AgentECHO : public Agent { public: AgentECHO(); // 建構子 virtual void recv(Packet *, Handler *); // 封包入口 protected: virtual int command(int argc, const char*const* argv); // 與TCL 溝通的界面 private: int my_addr; //用來儲存自己的 address, 與溝通用的port. int data_port; };
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(I) #include "AdhocEcho.h“ #include “ip.h“ // 用來處理ip 表頭的訊息 #include “packet.h“// 處理封包必備標頭檔 int hdr_ahe::offset_; // 為了正確處理自訂packet 標頭而宣告 // 自訂標頭檔的 wrapper class, 宣告十分制式,目的是為了讓使用者能在TCL中存取自訂packet 標頭, 用處很少 static class AHEHeaderClass : public PacketHeaderClass { public: AHEHeaderClass() :PacketHeaderClass("PacketHeader/AHECHOHEAD",sizeof(hdr_ahe)) { bind_offset( &hdr_ahe::offset_ ); } } class_ahe_hdr;
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(II) // 十分重要的 Wrapper class for our agent.講義第三頁 – 程式五 static class AHEClass : public TclClass { public: // Agent/ADECHO 是在TCL 中 create 這個 agent 時所需要的名字 AHEClass() : TclClass("Agent/ADECHO") {} // 這個class 實際上僅是創造一個agent class 並回傳. TclObject* create(int, const char*const*) { return ( new AgentECHO() ); } } class_ahe_agent;
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(III) AgentECHO::AgentECHO() : Agent(PT_AECHO) { } 1. 目前沒有需要初始化的部份,因此建構子沒有內容 2. “PT_AECHO” 表示AgentECHO agent 所使用的packet類別設為”PT_AECHO”. 利用此packet 類別,Agent可以決定對應的處理. 並且系統trace 輸出也將跟據此類別決定輸出的方式. packet類別可以為任意值,但必須在”../packet.h” 中稍加修改. (可參考講義第4頁)
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(IV) AgentECHO預設必須要實現的函式: command command是由tcl 對此agent class 下指令時,與c++程式溝通的介面. 目前我們讓使用者在劇本中可以對此agent 下三種指令, 分別為: AgentName myaddr Int1 AgentName myport Int2 AgentName ShowName 指令(1)與(2)是用來告知c++程式中,agnet的addr與溝通用的port. 指令(3) 則是要求該agent 列印出addr 與port, 可以當作debug 訊息.
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(V) command 副函式接收到的訊息使用argc 表示參數個數, argv陣列實際紀錄所收到的字串內容.我們可以把上列指令(1)想像成 AgentName myaddr Int1 argc=3 : argv[0] argv[1] argv[2] 判斷所輸入的指令的方式 1. 比對argc 數目 2. 比對 argv[1] 的值
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(VI) int AgentECHO::command(int argc, const char*const* argv) { if (argc == 2) { //AgentName ShowName if (strcmp(argv[1], "ShowName") == 0) { printf(" Hello World -- I am %d port=%d!!\n" ,my_addr,data_port); return (TCL_OK); } else if(argc==3){ // AgentName myaddr Int1 if (strcasecmp(argv[1], "myaddr") == 0) { my_addr= atoi(argv[2]); if (strcasecmp(argv[1], "myport") == 0) {// AgentName myport Int2 data_port= atoi(argv[2]); return Agent::command(argc, argv);
STEP 2. 以 C++程式碼實現 AgentEcho.cc 的細節(VII) 編輯Ns2目錄下的 Makefile, 將AdhocEcho/AdhocEcho.o 加到適當位置去. OBJ_CC = \ …… aodv/aodv_logs.o aodv/aodv.o \ AdhocEcho/AdhocEcho.o \ simulator.o \ 完成後,執行make depend, make. 若無錯誤,就完成了
STEP 3. 建立簡單TCL script 來測試AgentEcho 我們要執行實驗的指令: ./ns scripts/run.tcl -x 1200 -y 800 -stop 20 -tr TRACES/out.tr -mg AdhocEcho/sna_1 -sc movement_scenarios/scen-1200x800-30-500-10-1 -nn 30 -rp AdhocEcho 使用ns 主程式執行 scripts/run.tcl 的設定檔file. -x, -y 用來描述實驗範圍的大小為 1200x800 m2. -stop 設定此實驗模擬20 secs, -tr 則是指定log 檔的位址.
STEP 3. 建立簡單TCL script 來測試AgentEcho (I) ./ns scripts/run.tcl -x 1200 -y 800 -stop 20 -tr TRACES/out.tr -mg AdhocEcho/sna_1 -sc movement_scenarios/scen-1200x800-30-500-10-1 -nn 30 -rp AdhocEcho -mg 指定了本程式的劇本為AdhocEcho/sna_1, -sc 指定了節點的移動路徑設定檔file 為movement_scenarios/scen-1200x800-30-500-10-1。 -nn 指定了模擬實驗中的節點總數為50, -rp 則指定模擬的protocol 為adhocEcho(根據這個敘述,ns2將尋找並載入adhocEcho/adhocEcho.tcl .
STEP 3. 建立簡單TCL script 來測試AgentEcho (II) – 4個重要的scripts 1: srcipts/run.tcl: 用來建立adhoc nodes的設定檔, 此設定檔將自動建立 -nn 個節點. 此檔案無須修改,並在 srcipts 子目錄下面可以找到. 2: AdHocEcho.tcl: 用來描述建立一個adhoc node較底層的設定, 此檔案較為難懂,建議copy我的file,並稍加修改其內容即可(下面介紹要修改的部份), 注意此file名稱必須與資料夾名稱相同.
STEP 3. 建立簡單TCL script 來測試AgentEcho (III) – 4個重要的script 3: scen-1200x800-30-500-10-1: adhoc nodes 移動的路徑設定, 用setdest的程式來產生 4: sna_1: 實驗的劇本設定.用來指定實驗節點的互動方式. 由於大部分的運作邏輯利用c++ 實現在 AdEchoAgent的邏輯之中,所以此劇本大致上只是進行簡單的設定,起動與結束的動作.
STEP 3. 建立簡單TCL script 來測試AgentEcho (IV) 在AdHocEcho.tcl中,所需修改的程式部份為 create-client的副程式 ODMRPNode instproc create-client {} { $self instvar dmux_ mcast_dmux_ global RouterTrace AgentTrace opt #set s_agent [new Agent/IRODV1] set s_agent [new Agent/ADECHO] // 只需改這裡,改成我們的agent set id_ [$self id] set port $opt(uni_data_port) $s_agent myaddr $id_ $s_agent myport $port … return $s_agent }
STEP 3. 建立簡單TCL script 來測試AgentEcho (V) sna_1 的內容: 節點之間的互動 for {set i 0} {$i < $opt(nn)} {incr i} { set ag_($i) [$node_($i) create-client] // 呼叫我們剛剛修改的副程式,將agent 架構在node 上 $ns_ at 2.000 "$ag_($i) ShowName" // 要求 ns 在 2 sec 時,對每一個agent 下 ShowName的指令 } 試試看. 看一下效果如何.
補充: setdest 的用法 用 setdest 產生random walk 的移動 Setdest 的參數 在compile 時,會自動compile 出setdest 這個utility Setdest 的參數 usage: ./setdest -n <nodes> : 要移動的節點數 -p <pause time> : 移動間的休息時間 -s <max speed> : 移動速度 m/s -t <simulation time> : 模擬時間 -x <max X> -y <max Y> : 模擬範圍 X, Y
Ns2 一個常用的網路模擬器 Step4. 加入傳送與接收封包的功能
STEP 4. request/response 的能力 目標新功能 EchoAgent 可以接受來自ns 的 send 指令. 在收到send 指令之後, Echo Agent 將廣播一個Echo封包給其鄰居, 所有收到Echo封包的人將回送一個reply 封包.
目標分析 上面的需求可以分成: 在command 副函式中加入send指令的描述, 當Ns2 執行 send 指令時, echo_send 的副函式將被執行. 實現echo_send 的副函式,其內容為廣播一個echo-request 封包 改寫 recv 函式, 當確認收到echo 封包時, 呼叫 echo_recv 函式來回應 實現echo_recv 的副函式,其內容為當收到echo-request 時, 送出一個echo-reply 封包, 當收到echo-reply時,列印相關訊息
傳送Request (Send 的能力) 我們在command 副函式中,加入send 指令的描述 AgentECHO::command 副函式的內容將改成: int AgentECHO::command(int argc, const char*const* argv) // 程式九 { Tcl& tcl = Tcl::instance(); if (argc == 2) { if (strcmp(argv[1], "send") == 0) { echo_send(); return (TCL_OK); } …..
傳送Request (Send 的能力) (II) 當echo_send 被呼叫時,agent 將準備一個echo-request 的封包,廣播給所有的鄰居。 void AgentECHO::echo_send() // 程式十 Packet *pkt = allocpkt(); // Create New Packet hdr_cmn *ch = hdr_cmn::access(pkt); hdr_ip *iph=hdr_ip::access(pkt); hdr_ahe *aheh=hdr_ahe::access(pkt); iph->saddr()=my_addr; iph->sport()=data_port; iph->daddr()=IP_BROADCAST; iph->dport()=data_port; ch->ptype()=PT_AECHO; aheh->Type=0; // Request Packet aheh->FromNode=my_addr; aheh->FromTime=Scheduler::instance().clock(); target_->recv(pkt, (Handler*)0); } IP Information Fill AdHocEcho’s Header Send Packet.
接收封包 (Recv 的能力) (II) 當一個agent收到封包時,該封包將會被送到recv副函式 。 為了簡化recv的內容,我們多半只在recv中作分類的動作,然後就將不同的封包交給不同的副函式去處理。 void AgentECHO::recv(Packet* pkt, Handler *){// 程式 11 hdr_cmn *ch = hdr_cmn::access(pkt); if(ch->ptype()==PT_AECHO){ // 如果是AdHocEcho 封包,就交給echo_recv() echo_recv(pkt); } Packet::free(pkt);
echo_recv 詳細內容 void AgentECHO::echo_recv(Packet* pkt) // 程式 12 { hdr_ip *iph=hdr_ip::access(pkt); hdr_ahe *aheh=hdr_ahe::access(pkt); if(!aheh->Type){ // Recv Request Packet Packet *npkt = allocpkt(); // 產生Reply用的封包 hdr_ip *niph=hdr_ip::access(npkt); hdr_ahe *naheh=hdr_ahe::access(npkt); hdr_cmn *nch = hdr_cmn::access(npkt); niph->saddr()=my_addr; niph->sport()=data_port; niph->daddr()=iph->saddr(); niph->dport()=data_port; naheh->Type=1; // 設定為Reply用的封包 naheh->FromNode=aheh->FromNode; // 填入訊息 naheh->FromTime=aheh->FromTime; naheh->ReplyNode=my_addr; naheh->ReplyTime=Scheduler::instance().clock(); target_->recv(npkt, (Handler*)0); }else{ // 列印訊息 } echo_recv 詳細內容
試試看: Send/Request 的功能 我們重新make程式,並在sna_1 的最後加入: $ns_ at 12.0001 "$ag_(20) send" 執行實驗指令
Ns2 一個常用的網路模擬器 偵測 Time-out 的能力
STEP 5. 處理Time-out的能力 所謂的Timeout,就是說在一定的時間之內,某一個agent沒有收到該有的回應,因此該agent必須作一些動作來彌補這個意外。 如何知道,已經timeout ? 透過”鬧鐘”class 來達成
想像的劇本 我們要模擬的狀況: A送出一個requeste給B,而期望在1 sec 內收到B的回應,若無回應,則認為已經time-out。 則我們在NS2中要作的是,就是當A送出一個封包時,順便啟動一個會在1sec之後觸發的鬧鐘, 若在1sec內收到回應,則在收到的瞬間,關掉鬧鐘。 否則在1sec 結束後,鬧鐘將會觸發,A也將認為本次的連線是以timeout的情形結束。
目標分析 上面的需求可以分成: 創造一個”鬧鐘”的物件。 當agent 送出request封包時,也馬上設定/啟動鬧鐘 當收到回應封包時,便嘗試著關鬧鐘 一但鬧鐘響,便表示timeout已經發生
創造一個”鬧鐘”的物件 我們必須修改AdhocEcho.h,不僅是定義”鬧鐘”的class,並且要建立鬧鐘與我們的agent的互動。我們將我們鬧鐘物件稱為Echo_Timeout,
創造一個”鬧鐘”的物件(I) AdhocEcho.h,定義”鬧鐘”class -- Echo_Timeout 並且要建立鬧鐘與我們的agent的互動。 // 程式13 的上半部 class Echo_Timeout; // ”鬧鐘”class 的宣告 class AgentECHO : public Agent { friend class Echo_Timeout; // 設為friend class 可呼叫private method public: AgentECHO(); virtual void recv(Packet *, Handler *); void Recv_callback(Event* e); protected: virtual int command(int argc, const char*const* argv); ….. }
創造一個”鬧鐘”的物件(II) // 程式13 的下半部 private: int my_addr; int data_port; …… Echo_Timeout* EchoT; // agent 內部宣告一個時鐘 void echo_p2p_send(int dest); // 測試 time-out 的函式 Event *p2pSendEvent; // 為了取消鬧鐘設定的變數 void Recv_callback(Event *); // 與鬧鐘互動的函式 }; class Echo_Timeout : public Handler { // 鬧鐘class 的完整內容 public: Echo_Timeout(AgentECHO *a_) { a = a_; } // 鬧鐘觸發時會執行 handle 副函式 // handle 副函式會執行agent中的Recv_callback副函式 virtual void handle(Event *e) { a->Recv_callback(e); } AgentECHO *a; #endif // ns_ns_irreq_h
AdHocEcho Agent 的Time-out處理 為了創造EchoT的object(鬧鐘),我們在AgentEcho 的建構子中,加入了兩行敘述 AgentECHO::AgentECHO() : Agent(PT_AECHO) // 程式 14 { EchoT= new Echo_Timeout(this); p2pSendEvent=0; }
鬧鐘的使用與測試 為了測試這個時鐘,我們在command新增了 p2psend 的指令 此指令將要求一個輸入,表示要echo 的點,當此command 被執行時,agent 的echo_p2p_send副函式將被執行。 int AgentECHO::command(int argc, const char*const* argv) // 程式 15 { Tcl& tcl = Tcl::instance(); if (argc == 2) { ... } else if(argc==3){ … if (strcasecmp(argv[1], "p2pSend") == 0) { echo_p2p_send(atoi(argv[2])); return (TCL_OK); return Agent::command(argc, argv);
echo_p2p_send 詳細內容 void AgentECHO::echo_p2p_send(int dest) // 程式 16 { Packet *pkt = allocpkt(); hdr_cmn *ch = hdr_cmn::access(pkt); hdr_ip *iph=hdr_ip::access(pkt); hdr_ahe *aheh=hdr_ahe::access(pkt); fprintf(stderr,"%.5f at Node %d sned p2p << Request >> Packet.\n",Scheduler::instance().c iph->saddr()=my_addr; iph->sport()=data_port; iph->daddr()=dest; // 第一個不同 點,送給單一echo對象 iph->dport()=data_port; ch->ptype()=PT_AECHO; aheh->Type=0; aheh->FromNode=my_addr; aheh->FromTime=Scheduler::instance().clock(); target_->recv(pkt, (Handler*)0); p2pSendEvent= new Event(); // 第二個不同點,啟動鬧鐘 Scheduler::instance().schedule(EchoT, p2pSendEvent, 1);
鬧鐘的觸發 當鬧鈴啟動時,主程式中的Recv_callback(Event *) 副函式將自動被執行。 在此,我們只希望他列印一個Timeout 的訊息,因此,我們的Recv_callback(Event *) 副函式的內容如下: void AgentECHO::Recv_callback(Event* e){// 程式 17 fprintf(stderr,"%.5f at Node %d Timeout\n", Scheduler::instance().clock(),addr()); p2pSendEvent =0; }
把鬧鐘關掉 當我們收到正確資料時,我們要把鬧鐘關掉,因此,我們在echo_recv 的副函式中做了以下的更改。 void AgentECHO::echo_recv(Packet* pkt){ // 程式 18 { hdr_ip *iph=hdr_ip::access(pkt); hdr_ahe *aheh=hdr_ahe::access(pkt); if(!aheh->Type){ ….. // 收到request, 回應reply封包 }else{ // 收到reply封包,關掉鬧鐘,印出訊息 if(p2pSendEvent){ Scheduler::instance().cancel(p2pSendEvent); p2pSendEvent=0; } . // 列印訊息
試試看: TimeOut 的功能 我們重新make程式,並在sna_1 的最後加入: $ns_ at 12.0001 "$ag_(20) p2psend 10“ 執行實驗指令試試看