1. 程式人生 > >如何在ns2中實現一個簡單的網路協議

如何在ns2中實現一個簡單的網路協議

使用IE不會有顯示的問題Firefox有的程式碼顯示不出來;

本文通過實現一個簡單的傳輸協議來說明如何在 ns2 中實現網路協議,當然,這個協議非常簡單,但是在 ns2中實現協議(不是修改)的流程大體就是這個樣子的了。我們稱這個簡單的協議做: simple_trans 協議,我們一步一步來,把 simple_trans 這個協議慢慢做的複雜。首先我想要明確一個概念:什麼是在 ns2 中實現網路協議,不把這個問題搞明白我們都不知道自己在做什麼。網路協議顧名思義網路上執行的協議,網路是由關係(無論什麼關係)組成的,在這個網路上執行的規則(無論是優化網路資料傳輸還是共享網路資訊)就叫做協議,所以我覺得把協議理解為強邏輯的規則是沒有問題的。我們實現一個網路協議的前提是這個協議被設計出來,所以我們先要想好我們所要實現的協議是要用來做什麼事情的;回到 ns2 , ns2 幫我們實現好了一個框架,這個框架給我們提供了資料包初始化,鏈路連線,資料包傳遞路由等功能,也就是說我們只要搭建好我們的邏輯就可以完成協議的模擬了,在 ns2 中我們通過對資料包型別、傳送資料包邏輯等等進行控制。這就好比於 ns2 給我們提供了一個鐵路網,火車需要的電也有了,火車不夠了還可以生產,我們在 ns2 中實現協議就是要對火車進行排程,何時到站,到站後如何執行等等就是協議的內容。

       下面就從我們的 simple_trans開始 說起,在這個協議裡,首先我們要實現的任務非常簡單,簡單到什麼程度了呢,簡單到這個協議就是 a 節點對 b 節點說一句話:“ hi!, I’m a ”。不要笑,這也是一個協議。要在 ns2 上完成這個任務,我們首先要給 simple_trans 這個協議起個名字使得 ns2 可以發出這個協議的資料包並且認得這個協議發出的資料包,現在開始就是第一步了。

1, 在 NS_HOME/common/packet.h 的 enum packet中 加入協議資料包名稱 PT_SIMPLE_TRANS_PACKET (必須的,注意不要加錯地方,最好加在倒數第二的地方),在 class p_info 中加入name_[PT_SIMPLE_TRANS_PACKET] = "simple_trans_packet" (非必須的)。 Packet.cc 就不要動了。

2,       為了我們協議的獨立性、好看性,我們在 NS_HOME 根目錄下建立一個資料夾,我就叫他 kgn ,好在 kgn目錄(也就是 NS_HOME/kgn )目錄下給協議的主角: simple_trans.h&simple_trans.cc 。兩個空檔案沒什麼用,下面我們新增協議內容。

3,       simple_trans.h 內容:

  1. #ifndef ns_simple_trans_h  
  2. #define ns_simple_trans_h  
  3. #include "agent.h"  
  4. #include "tclcl.h"  
  5. #include "packet.h"  
  6. #include "address.h"  
  7. #include "ip.h"  
  8. #define PROTOCOL_DEFAULT_PORT 1023  
  9. #define PROTOCOL_INIT_SYN 1  

首先我們引用一些需要用到的標頭檔案,然後我們定義了兩個巨集,第一個是我們 simple_trans 協議預設傳輸的埠(這方面如果有所疑問請參考這裡 ),第二個是我們僅有的一條指令:同步指令(類似於 TCP 協議中三次握手的第一步,事實上我們的這個協議最終就是要實現一個簡化版的三步握手)。繼續看:

  1. struct hdr_simple_trans {  
  2.     int type;  
  3.     static int offset_;  
  4.     inline static int& offset() {  
  5.         return offset_;  
  6.     }  
  7.     inline static hdr_simple_trans * access(const Packet * p) {  
  8.         return (hdr_simple_trans*) p->access(offset_);  
  9.     }     
  10. };  

這些可以當做領導講話的開頭部分內容。就是定義一個我們協議的頭所包括的內容,只有 type 這個是我定義的,其他的內容是 ns2 系統需要的。再繼續:

  1. class simple_trans_agent : public Agent {  
  2.     public :  
  3.         simple_trans_agent();  
  4.         virtual void recv(Packet *, Handler *);  
  5.         void send_simple_msg(int type, int target);  
  6.         int get_target(){ return simple_target; }  
  7.     protected:  
  8.         int simple_target;  
  9.         int simple_port;  
  10.         int command(int argc, const char*const*argv);  
  11. };  

這裡就是我們定義的負責“排程火車”的功能的類了。繼承的是 agent 類,在 ns2 中,這個 agent 不可小覷,他是我們可以產生資料包、傳送資料包、接收資料包的地方,包括的 target 變數就是資料包傳送給的下一個目標。 recv函式會在模擬的過程中“自動”的收到網路上傳輸的資料包(更深層次的是經過了地址和埠過濾器);send_simple_msg 函式用來執行建立併發送資料的功能; get_target 就不用解釋了(介面保護)。接下來是我們在協議制定過程中經常會用到的 timer 的定義, timer 顧名思義是一個定時器(鬧鐘)在到時時候會呼叫一個 expire(超時)函式,這個被執行的超時函式的內容就是我們所感興趣的,因為通過 timer 我們可以實現很多邏輯。

  1. class SYNTimer : public TimerHandler {  
  2.     public:  
  3.         SYNTimer(simple_trans_agent* t) : TimerHandler(), t_(t) {  
  4.         }  
  5.         inline virtual void expire(Event *);  
  6.     protected:  
  7.         simple_trans_agent* t_;  
  8. };  

我們只要實現 expire 函式即可, timer 的初始和使用見 simpe_trans.cc 檔案:

  1. SYNTimer *syn_timer = new SYNTimer(this);  
  2. syn_timer->resched(1.00);  

resched 用來給“鬧鐘上弦”。

  1. void SYNTimer::expire(Event *){  
  2.     t_->send_simple_msg(PROTOCOL_INIT_SYN, t_->get_target());  
  3.     this->resched(1.00);  
  4. }  

expire 可以實現我們的“理想”了,譬如,我們到時了就傳送我們的 SYN 資訊給我們的目標節點(目標節點通過tcl 檔案定義,下文中我們會見到)。

4,       simple_trans.cc 內容:

  1. int hdr_simple_trans::offset_;  
  2. static class simple_transHeaderClass : public PacketHeaderClass {  
  3.     public:  
  4.         simple_transHeaderClass() : PacketHeaderClass("PacketHeader/simple_trans",sizeof(hdr_simple_trans)) {  
  5.             bind_offset(&hdr_simple_trans::offset_);  
  6.         }  
  7. } class_simple_transhdr;  
  8. static class simple_transClass : public TclClass {  
  9. public:  
  10.     simple_transClass() : TclClass("Agent/simple_trans") {}  
  11.     TclObject* create(intconst char*const*) {  
  12.         return (new simple_trans_agent());  
  13.     }  
  14. } class_simple_trans;  
  15. simple_trans_agent::simple_trans_agent() : Agent(PT_SIMPLE_TRANS_PACKET),   
  16.         simple_target(-1), simple_port(PROTOCOL_DEFAULT_PORT) {  
  17.     bind("simple_target_", &simple_target);  
  18.     bind("simple_port_", &simple_port);  
  19. }  

這個又是八股文,前面幾個類照葫蘆畫瓢即可,如果想要理解是什麼意思可以參考我的文章 ,最後一個我們 bind了幾個變數,這幾個變數通過繫結就可意思讓我們通過 tcl 指令碼方便的改變他們的值了(不需要重新編譯 c++ 檔案)。

5, 在這個檔案中我們主要注意這麼幾點:

a)       在 send_simple_msg 中資料包的生成 Packet* pkt = allocpkt() ;

b)        資料包的訪問: hdr_ip *iph = hdr_ip::access(pkt) ;

c)        資料包 ip 地址和埠號的設定(從這裡我們可以看出實現的是一個應用層協議);

d)       傳送資料包 send( pkt, 0 ) ,我們可以不用去管 0 是什麼意思;

e)        Command 命令中不要忘記 return (TCL_OK) 這句話,否則會出錯的。

f)         在 recv 函式中實現我們的簡單邏輯:顯示出我們收到了來自對方的一個 simple_trans 的資料包。

6,       看我們這兩個巨集命令:

  1. #define NOW Scheduler::instance().clock()  
  2. #define MYNODE Address::instance().get_nodeaddr(addr())  

這兩個命令給我們程式設計提供幫助,分別顯示系統時間和得到當前節點的地址,也許以後我們會用得著。

7, 在 tcl 指令碼中我們需要使用我們的 simple_trans 協議:

  1. set sT1 [new Agent/simple_trans]  
  2. $sT1 set-target [AddrParams addr2id [$n1 node-addr]]  
  3. $n0 attach $sT1 1023  
  4. set sT2 [new Agent/simple_trans]  
  5. $n1 attach $sT2 1023  
  6. ... ...  
  7. $ns at 1.0 "$sT1 begin"  

在 tcl 中 new 一個物件,比如 sT1 之後我們要將其 attach 到所屬的節點上,注意最後一個 1023 ,這是我們 attach到節點上的給我們 simple_trans 協議分配的埠(深層次的意思是埠分類器會把目的埠是 1023 的資料包分給sT1 )。 begin 方法是在 command 中實現的,回過頭到 simple_trans.cc 中可以看到他的意思,我們可以好好理解一下 command 中函式和 tcl 中的使用關係。

8, 最後一步,就是編譯我們整個協議將其鍵入到 ns 中了,編譯前我們要修改 makefile 檔案,由於我們是在NS_HOME/kgn 目錄中所以, makefile 需要修改的有兩個地方:在 INCLUDES = 中加入 -I./kgn ,加入這個的好處就是我們在其他目錄使用 simple_trans.h 的時候不用將 kgn 次級目錄包含進去;在 OBJ_CC = 中加入kgn/simple_trans.o / 。好了大功告成,下面回到 NS_HOME 目錄下 make 一下,如果成功,我們執行一下我們的 tcl 指令碼,看看是不是真的可以運行了呢。

小結:到了這裡我們已經添加了一個簡單的協議了,好了,有的人會說了,這麼簡單的協議有什麼用呢?那好,我們想一想我們有什麼可以改進的嗎?以上的協議我們叫做 simpe_trans 協議 0.1 版,那麼我們看看 0.2 版給我帶來了什麼新的變化。

ACK timer

 

首先要做的就是協議的複雜化,我們將協議改為三次握手過程如圖所示:


這個過程對應以下程式碼(修改simple_trans.h):

  1. #define PROTOCOL_INIT_SYN 1  
  2. #define PROTOCOL_INIT_SYN_ACK 2  
  3. #define PROTOCOL_INIT_ACK 3  
  4. #define INTERVAL 0.3  
  5. class simple_trans_agent;  
  6. enum simple_state{  
  7.     CLOSED,   
  8.     SYN_SENT,  
  9.     SYN_RCVD,  
  10.     ESTABLISHED  
  11. };  

其中 C->CLOSED , SS->SYN_SENT , SR->SYN_RCVD , E->ESTABLISHED 為節點可能處於的狀態在傳送或接受 SYN 和傳送 SYN-ACK 接受 ACK 後的變化,而兩個 timer 的作用就是使得沒有正確到底目的地的資料包可以被重新發送,當然這些 timer 需要在適當的時機取消比如: ack_timer->cancel() ,取消 timer 使用 cancel 函式即可。具體程式碼實現參考 0.2 版本的程式碼。那麼現在我們重新 make 編譯我們的程式,我們會發現兩個節點可以通過三次握手建立起來一個簡單的連結了,可以說我們在有這個簡單的可以建立連線的程式之後我們馬上想到是不是還可以傳送資料呢,在 ns2 中,資料的傳送,我們常見的如 CBR 或者 FTP ,都可以傳送資料但是他們之間有很大的不同 CBR 使用的是 trafficgenerater ,而 FTP 可以看成是一個帶傳送資料包的 agent ,現在為了讓我們的simple_trans 協議可以在建立起連線以後傳送資料,我們就有了兩種選擇,是繼承 trafficgenerater 成為資料傳送源呢,還是類似 FTP 使用 agent 傳送資料,考慮到我們協議的簡潔易懂性,我們直接使用一個 timer ,在每次 timer到時的時候都利用 simple_trans 的 send 函式傳送一個具有 PROTOCOL_DATA 型別(標識是一個數據)的包給通訊對端( CN )。在 sendmsg 函式中的實現如下:

  1. hdr_rtp* rh = hdr_rtp::access(p);  
  2.     hdr_simple_trans *shdr = hdr_simple_trans::access(p);  
  3.     hdr_ip* ih = hdr_ip::access(p);  
  4.     double local_time = NOW;  
  5.     hdr_cmn::access(p)->size() = size;  
  6.     hdr_cmn::access(p)->timestamp() =  
  7.                 (u_int32_t) (SAMPLERATE * local_time);  
  8.     rh->seqno() = seqno++;  
  9.     ih->daddr() = simple_target;  
  10.     ih->dport() = simple_port;  
  11.     ih->saddr() = MYNODE;  
  12.     shdr->type = PROTOCOL_DATA;  
  13.     target_->recv(p);  

這裡面我們還可以通過 RTP 協議給每一個包設定序列號,當然也可以在 hdr_simple_trans 中新增一個 seq 的屬性。當然我們的協議升級到 0.3 版本後的變化並不只是有這些而已。我們還將 simple_trans 協議的資料包的大小以及傳送頻率設定成可變的等,具體可以參考 0.3 的程式碼。

小結:通過以上的設計,我們初步有了一個可以建立連線併發送資料的協議,什麼?像是 SIP 協議,沒錯我們也可以將我們的程式叫做一個簡單的會話發起協議,當然你可以實現的更加複雜。至此,我們在 ns2 中新增一個基本網路協議的事情已經完成了,我們注意到:不同的協議使用節點上的不同的埠,這樣的協議是不能夠影響到諸如路由、無線鏈路等協議的結果的,所以並不是所有的 ns2 中的協議都可以這麼新增,我們還可以修改節點資料結構等方法新增我們自己的一些修改進 ns2 達到模擬的目的,所以這篇文章的目的還是介紹如何在 ns2 中實現協議的基礎,我們要根據我們自己的模擬需要來設計我們的程式。通過以上的介紹我們應該掌握的是在 ns2 中傳送資料的方法、 ns2 中 timer 的使用方法等等技巧。下面我介紹一個比較有意思的利用我們的 simple_trans 做的協議修改實驗:新增無線節點丟包模型,在這裡主要參考的是柯志亨老師的實現方法,但是在丟包方面我這裡做的對原有協議破壞性更多(更不合理吧),我們將演示當兩個無線節點距離增大的時候會丟失資料包並且我們的 ACKTimer 以及SYNTimer 的作用。好,下面就是如何修改的過程了:

在 NS_HOME/mac 目錄下的 wireless-phy.cc 的 380 行左右,我們新增如下程式碼:

  1. //error model.  
  2.     hdr_cmn *hdr_err = HDR_CMN(p);  
  3.     hdr_simple_trans *sh = hdr_simple_trans::access(p);  
  4.     double ratio = Pr/RXThresh_;  
  5.     double std = error_modle_lf(ratio);  
  6.     //printf("wireless-phy model receive packet ratio=%lf std=%lf/n",ratio,std);  
  7.     if (hdr_err->ptype() == PT_SIMPLE_TRANS_PACKET){  
  8.         if (!sh->error){  
  9.             double tmp=((double)rand())/RAND_MAX;  
  10.             if (tmp>std){  
  11.                 sh->error = false;  
  12.             }else{  
  13.                 sh->error = true;  
  14.                 //printf("wireless-phy error model set the packet error/n");  
  15.             }  
  16.         }  
  17.     }  
  18.     //end of error model.     

  我們修改的是 WirelessPhy::sendUp(Packet *p) 函式,在傳送資料包之前我們檢查資料包中 simple_trans 協議的資料包,並將該資料包中在 hdr_simple_trans 中定義好的 error 屬性置為 true (說明這個資料包出錯),實現資料包出錯分佈的函式 error_modle_lf ,這是一個拉格朗日差值函式的實現:

  1. double error_modle_lf(double ratio){  
  2.     if(ratio >1.5)return 0;  
  3.     double x[6] = {1,1.1,1.2,1.3,1.4,1.5};  
  4.     double y[6] = {1,0.5,0.3,0.1,0.02,0};  
  5.     double res = 0;  
  6.     for(int i = 0; i < 6; i++){  
  7.         double temp = 1;  
  8.         double temp1 = 1;  
  9.         for(int j = 0; j < 6; j++){  
  10.             if(i == j)continue;  
  11.             temp *= (x[i] - x[j]);  
  12.             temp1 *= (ratio - x[j]);  
  13.         }  
  14.         res += (temp1 / temp) * y[i];  
  15.     }  
  16.     return res;  
  17. }  

顯然,我們設計的是無線節點離基站越遠對包個數越多。

1,  在 simple_trans.cc 中新增 if( shdr->error )return ,這樣錯誤的包我們就“裝作”收不到了

2,  這裡補充說明,柯志亨老師的錯誤模型實現是基於無線層的,出錯了就真的不發或者重發,而我的實現可以說是假的,還會造成無線網路的吞吐,但是還是可以演示無線丟包情況的,具體結果可以編譯我稱作 0.4 版本的程式執行。可以將包的序列號畫出來,這樣會更形象的展現丟包情況。

3,  我們將包序列號、包收到時間等等資訊都通過 printf 函式打印出來,這樣我們就可以不用去考慮如何通過 trace檔案來分析得到資料,這種方法有的時候更加有效,我們不必去了解 trace 機制,這也算是一個捷徑了。

總結:

       Ns2 作為一個在科研領域應用廣泛的模擬器有著其內在的很多優勢的:開源協議修改自如、分裂設計可設計不同的模擬場景而不需要修改協議程式碼,但是,我們在做網路協議的研究的時候往往會發現 ns2 現有的協議不足以完成我們的模擬,這是就需要自己設計協議或者修改現有的協議,所以通過對這個簡單的 simple_trans 協議的實現我們可以更加的有的放矢,知道如何在那裡修改 ns2 的協議,雖然 simple_trans 只是一個超級笨的協議,但是它已經展現了基本的協議設計技巧:整合 agent 、 timer 的使用、協議包頭設計等等。如果我們能夠再將 ns2 有線無線節點結構、路由模組、無線 mac 等等這些程式碼仔細研讀,那麼到時你就會發現在 ns2 上面實現一個協議倒不是難事,反而是在協議自身的設計上,這就和我們高階程式語言一樣,語言的學習不是難事,而真正熟練的利用語言解決問題才是我們的學習目標。

擴充套件實驗:

1,考慮路由協議的實現和我們這個application的協議的實現有何異同?

2,實現一個單工的停等協議?

3,實現資料流的優先順序(同一個節點上安裝多個simple_trans協議【埠要改】)以考察QoS的概念?

 例項平臺:ns-2.31+cygwin+gcc3.4.4