建立Agent範例-ECHO

指導教授: 吳東光老師

作者: 張瀹鐏

 

本文參考http://www.isi.edu/nsnam/ns/tutorial/index.html之範例製作。

文件日期:2001/12/11

 

功能說明:

本範例之目的為,計算單一節點間的傳遞回應時間回應時間,並將之顯示於Console Mode之下。節點間為直接溝通,並無經由其他節點轉傳封包。

 

顯示範例:

模擬連結時,顯示畫面如下所示:

C:\NetSim>ns echo.tcl

node 0 received ping answer from  2 with round-trip-time 40.0 ms.

node 2 received ping answer from  0 with round-trip-time 40.0 ms.

node 0 received ping answer from  2 with round-trip-time 40.0 ms.

node 2 received ping answer from  0 with round-trip-time 40.0 ms.

 
 

 

 

 

 

 


上圖顯示之畫面,為本範例於有線連結之模擬環境中所作之範例。

 

一、建立EchoAgent協定

()建立EchoAgent所需之封包格式

struct hdr_echo {

        double send_time;

        char state;

        static int offset_;

        inline static int& offset() { return offset_; }

        inline static hdr_echo* access(const Packet* p) {

                return (hdr_echo*) p->access(offset_);

        }

};

 
 

 

 

 

 

 

 

 

 



1.1

 

首先,由於本範例之目的,希望輸出兩節點之間的回應時間。因此在自訂的封包格式中,需紀錄時間以及紀錄狀態的資訊。

於圖1.1中可見,send_time為紀錄發送端傳送要求回應的時間,state為紀錄封包狀態的資訊;系統時間可由Scheduler::instance().clock()函式取得,若state = 0時,則表示該封包為要求回應之封包,state = 1時,則表示該封包為目的端回應封包。

由於本封包為自訂封包,NS2無法直接取用;因此為了提供NS2其他元件可以使用此自訂封包struct hdr_echo{},因此必須給予一個可供PacketManager存取的地方。因此必須於自訂封包內加入offset_參數,並經由Packet::access()函式取得struct hdr_echo內的資料。

如此一來,欲取得struct hdr_echo中的資料時,可將接收(或傳送)Packet傳入Packet::access()中,並經由struct *hdr_echo中取得所需的資料 (見圖1.6)

     

()繼承Agent建立EchoAgent類別

class EchoAgent : public Agent {

public:

        EchoAgent();

        int command(int argc, const char *const *argv);

        void recv(Packet *, Handler *);

};

 
 

 

 

 

 

 

 


1.2

 

        class Agent{}為建立協定的base class,因此欲建立新的協定時,必須繼承該物件。

 

      ()建立C++TCL間的接口模組

int hdr_echo::offset_;

static class EchoHeaderClass : public PacketHeaderClass {

        public:

        EchoHeaderClass() : PacketHeaderClass("PacketHeader/ECHO",

                                                HDR_ECHO_SIZE) {

                bind_offset(&hdr_echo::offset_);

        }

}class_echohdr;

 

static class EchoClass : public TclClass {

        public:

        EchoClass() : TclClass("Agent/ECHO") {}

        TclObject * create( int, const char *const *) {

                return (new EchoAgent());

        }

}class_echo;

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


1.3

 

PacketHeaderClass{}為管理封包表頭之用的類別,因此我們必須給予該封包表頭一個名稱,並標明其繼承階層(如上圖紅字所示)。而TclClass{}所扮演的角色為提供Tcl取得C++物件的窗口,因此也需給予自訂協定名稱以及繼承階層。

 

EchoAgent::EchoAgent() : Agent(PT_ECHO) {

}

 
      ()給予EchoAgent封包名稱

 

 

 

1.4

 

Agent之建構子,其目的為給予此Agent一個固定名稱,以提供NS2辨別封包型態。如上圖所示,我們給予此Agent的封包型態名稱為PT_ECHO。可經由struct hdr_cmn::ptype()取得該名稱以作為判斷之用。

 

下列兩節介紹於Agent中必須,且最重要的兩部分函式

Agent::command(int argc, const char*const* argv);

Agent::recv(Packet* pkt, Handler*);

 

(5)撰寫EchoAgent::command()程式碼

int EchoAgent::command(int argc, const char*const* argv) {

        if (argc == 2) {

                if (strcmp(argv[1], "send") == 0) {

                Packet* pkt = allocpkt();

                struct hdr_echo* hdrcom = (hdr_echo*)hdr_echo::access(pkt);

                hdrcom->state = 0;

                        hdrcom->send_time = Scheduler::instance().clock();

                        send(pkt, 0);

                        return (TCL_OK);

                }

        }

      return (Agent::command(argc, argv));

}

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 


1.5

 

此函式之作用為接收並執行由TCL中下達之命令。如圖3.x所示,當我們對TCL物件下達send指令時,該send指令會送到物件的command此函式中進行處理。由上圖可得知,其參數有兩個的時候,第二為指令參數。當接收到send指令時,會先配置記憶體位置給封包,並將各種所需資訊寫入自訂的封包格式中,然後再將封包透過Agent::send()傳送出去;若以上動作皆成功時,則回傳一個TCL_OK訊息回去給上一層呼叫者。

 

(6)撰寫EchoAgent::recv()程式碼

void EchoAgent::recv(Packet* pkt, Handler*){

        struct hdr_ip *hdrip = (hdr_ip*)hdr_ip::access(pkt);

      struct hdr_echo *hdrecho = (hdr_echo*)hdr_echo::access(pkt);

      if (hdrecho->state == 0) { //表示接收到ping要求.

            double stime = hdrecho->send_time;

                Packet::free(pkt);

                Packet* pktret = allocpkt();

                struct hdr_echo *hdrret = (hdr_echo*)hdr_echo::access(pkt);

                hdrecho->state = 1;

                hdrecho->send_time = stime;

                send(pktret, 0);

        } else { //收到ping要求的回應

                char out[100];

        //下面的" %s showecho %d %3.1f"是對應到tcl程式中的 gotit函式

                sprintf(out, "%s showecho %d %3.1f", name(),

                        hdrip->src_.addr_ >> Address::instance().NodeShift_[1],

                        (Scheduler::instance().clock()- hdrecho->send_time) * 1000);

            Tcl& tcl = Tcl::instance(); //形成一個實體

            tcl.eval(out);

                Packet::free(pkt);

        }

}

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


1.6

 

Agent中,另一個重要函式為Agent::recv(Packet* pkt, Handler*),於本範例中將之改寫為符合我們需求的處理方式,我們在此函式中處理接收到封包的動作。

首先我們經由struct hdr_ip{}以及struct hdr_echo{}等結構抓出所需的資料進行判斷。第一,我們先判斷接收到的封包為何種型態;在(1)中有提到,本範例封包的狀態有兩種,一是要求回應,一是接收回應。當接收端收到state = 0的封包時,它就會將封包內的時間紀錄下來,並將此時間紀錄寫入一個新的封包中,而state也改寫成1,再利用Agent::send()傳回來源端。

當來源端收到封包時,同樣也去檢查state的狀態,當它發現state = 1時,則可以得知該封包為目的端處理過後的封包;在系統時間後,將之減去由封包取得的時間紀錄,即可得到目的端的回應時間。

而我們可以經由tcl.eval()提供的支援,呼叫TCL中的函式。如上圖所示,我們呼叫了TCL中的showecho函式,並且傳遞兩個參數,nodeID以及回應時間。

 

二、讓NS2認得自訂協定

 

經由第一章的介紹,我們已經建立出一個簡單的Agent,而本章節所描述的就是如何將Agent入NS2中,讓我們能夠經由TCL去操作位於NS2中的自訂協定。

 

enum packet_t {

        PT_TCP,

        PT_UDP,

        …………

        PT_AODV,

        PT_ECHO, //新增於此

        …………

        // insert new packet types here

        PT_NTYPE // This MUST be the LAST one

};

 
(1)於packet.h中新增EchoAgent封包名稱

 

 

 

 

 

 

 

1.7

 

 

 

2.1

 

首先,我們必須在packet.h中新增一個自訂封包的名稱,以便讓CMUTrace類別能夠認得我們自訂的封包。

 

class p_info {

public:

        p_info() {

                name_[PT_TCP]= "tcp";

                name_[PT_UDP]= "udp";

                ……

                name_[PT_AODV]= "AODV";

                name_[PT_ECHO]= "ECHO";

                ……

}

};

 
(2)於packet.h中的p_info{}加入自訂協定的名稱

 

 

 

 

 

 

 

 

 

 

 

 

2.2

 

此處的名稱是作為當經由TCL呼叫時,所訂定的協定名稱。

 

void CMUTrace::format(Packet* p, const char *why){

        ……

        case PT_GAF:

                break;

        case PT_ECHO:

                break;

        ……

}

 
(3)於CMUTrace中新增如何紀錄此封包

 

 

 

 

 

 

 

 

 

2.3

 

當我們的封包在傳遞時,必須留下紀錄以作為實驗、觀察之用,因此必須攔截封包以進行紀錄。而此處所示,乃為攔截PT_ECHO封包;但由於我們不需針對此範例做特別之紀錄,因此並未給予任何處理方式而直接跳離處理圈。若沒有加入此攔截的話,該封包會傳遞至default處,而顯示接收到錯誤型態的封包,並且跳出整個處理程序,而無法進行我們所需的實驗。

 

Simulator instproc create-wireless-node args {         

        ……

    switch -exact $routingAgent_ {

        ECHO {

                        set ragent [$self create-echo-agent $node]

        }

….

}

}

 
(4)於 ns-lib.tcl 中新增攔截自訂協定名稱的選擇項

 

 

 

 

 

 

 

 

 

 

2.4

 

由於在進行無線環境模擬時,於TCL主程式中,我們需透過 node-config去設定node的選項,因此先於create-wireless-node {} 中,新增一個自訂協定名稱的呼叫項。

 

Simulator instproc create-echo-agent { node } {

        #  Create ECHO routing agent

        set ragent [new Agent/ECHO [$node id]]

        $self at 0.0 "$ragent start"

        $node set ragent_ $ragent

        return $ragent

}

 
(5)於 ns-lib.tcl 中新增可供 TCL 呼叫的建構函式

    

       

 

 

 

 

 

 

2.5

 

然後在 ns-lib.tcl中新增可供wireless-node建構出實體的函式。透過此函式來呼叫自訂協定的物件,並隨即將之啟動。

 

OBJ_CC = \

        ……

        aodv/aodv_logs.o aodv/aodv.o \

        Echo/echo.o\

        ……

        simulator.o \

        $(OBJ_STL)

 
(6)於makefile.vc中新增Echo.o

 

 

 

 

 

 

 

 

2.6

在新增自訂協定,並且將其NS2後,我們可以利用nmake f makefile.vc來編譯出一個新的NS2解譯程式。

因此我們必須告訴編譯器應該編譯的檔案在哪裡。

 

三、利用TCL程式操控C++物件

#Define a 'recv' function for the class 'Agent/ECHO'

Agent/ECHO instproc showecho {from rtt} {

        $self instvar node_

        puts "node [$node_ id] received ping answer from \

              $from with round-trip-time $rtt ms."

}

 
(1)輸出經由物件傳遞過來之訊息

 

 

 

 

 

 

 

3.1

 

                提供經由tcl.eval()函式呼叫的TCL函式。此函式處理顯示回應輸出的要求。而其中的$from以及$rtt參數乃是經由圖1.6中所述,由out字串中傳遞而來。

 

無線連結之設定:

set val(nn) 50

set val(rp)  ECHO

 
(2)TCL撰寫

 

 

 

 

有線連結之範例程式:

set ns [new Simulator]

       

set n0 [$ns node]

set n1 [$ns node]

$ns duplex-link $n0 $n1 1Mb 10ms DropTail

       

set p0 [new Agent/ECHO]

$ns attach-agent $n0 $p0

set p1 [new Agent/ECHO]

$ns attach-agent $n2 $p1

$ns connect $p0 $p1

       

$ns at 0.2 "$p0 send"

$ns at 0.4 "$p1 send"

$ns run

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


3.2

       

如上圖紅字所示,可以經由TCL產生一個ECHO新物件,並利用它去進行封包傳輸的處理。