這個章節最主要的目的就是希望使用者可以學會如何新增或修改ns2的核心模組,更明確的說就是去新增和修改[*.cc, *.h]檔案,以筆者和筆者朋友學習ns2的經驗來說,這需要花很多時間和很大的勇氣,時間是花來找資料,勇氣是用來承受當程式寫不好時,ns2可能隨時會當掉。不過別怕,只要跟著筆者所介紹的方法,一定可以成功,若是不成功,就寫信給筆者吧。

        若是一開始就要叫ns2的新手去增加的模組,新手一定會哇哇叫,所以筆者不會這樣做,筆者先教大家拷貝ns2已經有的模組檔,但改成別的名字,這樣就可以輕鬆且很明確知道該如何增加模組。

 

(1)    打開cygwin的命令視窗,把路徑切換到queue的目錄下。

cd  ns-allinone-2.27/ns-2.27/queue

 

(2)    拷貝drop-tail.[cc, h]myfifo.[cc.h]

cp  drop-tail.cc  myfifo.cc

cp  drop-tail.h   myfifo.h

 

(3)    使用文字編輯軟體去修改myfifo.hmyfifo.cc(因為我們的環境是在windows下,所以建議可以使用ultra-edit來修改。

a.       先修改myfifo.h,使用取代的功能把所有DropTail改成myfifo,另外,把drop_tail改成myfifo

#ifndef ns_myfifo_h

#define ns_myfifo_h

 

#include <string.h>

#include "queue.h"

#include "config.h"

 

/*

 * A bounded, drop-tail queue

 */

class myfifo : public Queue {

  public:

        myfifo() {

                q_ = new PacketQueue;

                pq_ = q_;

                bind_bool("drop_front_", &drop_front_);

                bind_bool("summarystats_", &summarystats);

                bind_bool("queue_in_bytes_", &qib_);  // boolean: q in bytes?

                bind("mean_pktsize_", &mean_pktsize_);

                //              _RENAMED("drop-front_", "drop_front_");

        }

        ~myfifo() {

                delete q_;

        }

  protected:

        void reset();

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

        void enque(Packet*);

        Packet* deque();

        void shrink_queue(); // To shrink queue and drop excessive packets.

        PacketQueue *q_;    /* underlying FIFO queue */

        int drop_front_;        /* drop-from-front (rather than from tail) */

        int summarystats;

        void print_summarystats();

        int qib_;             /* bool: queue measured in bytes? */

        int mean_pktsize_;    /* configured mean packet size in bytes */

};

 

#endif

 

b.      再修改myfifo.cc,使用取代的功能把所有DropTail改成myfifo,另外,把drop_tail改成myfifodrop-tail改成myfifo

#include "myfifo.h"

 

static class myfifoClass : public TclClass {

 public:

        myfifoClass() : TclClass("Queue/myfifo") {}

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

                return (new myfifo);

        }

} class_myfifo;

 

void myfifo::reset()

{

        Queue::reset();

}

 

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

        if (argc==2) {

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

                        print_summarystats();

                        return (TCL_OK);

                }

               if (strcmp(argv[1], "shrink-queue") == 0) {

                       shrink_queue();

                       return (TCL_OK);

               }

        }

        if (argc == 3) {

                if (!strcmp(argv[1], "packetqueue-attach")) {

                        delete q_;

                        if (!(q_ = (PacketQueue*) TclObject::lookup(argv[2])))

                                return (TCL_ERROR);

                        else {

                                pq_ = q_;

                                return (TCL_OK);

                        }

                }

        }

        return Queue::command(argc, argv);

}

 

/*

 * drop-tail

 */

void myfifo::enque(Packet* p)

{

        if (summarystats) {

                Queue::updateStats(qib_?q_->byteLength():q_->length());

        }

 

        int qlimBytes = qlim_ * mean_pktsize_;

        if ((!qib_ && (q_->length() + 1) >= qlim_) ||

      (qib_ && (q_->byteLength() + hdr_cmn::access(p)->size()) >= qlimBytes)){

                // if the queue would overflow if we added this packet...

                if (drop_front_) { /* remove from head of queue */

                        q_->enque(p);

                        Packet *pp = q_->deque();

                        drop(pp);

                } else {

                        drop(p);

                }

        } else {

                q_->enque(p);

        }

}

 

//AG if queue size changes, we drop excessive packets...

void myfifo::shrink_queue()

{

        int qlimBytes = qlim_ * mean_pktsize_;

        if (debug_)

                printf("shrink-queue: time %5.2f qlen %d, qlim %d\n",

                       Scheduler::instance().clock(),

                        q_->length(), qlim_);

        while ((!qib_ && q_->length() > qlim_) ||

            (qib_ && q_->byteLength() > qlimBytes)) {

                if (drop_front_) { /* remove from head of queue */

                        Packet *pp = q_->deque();

                        drop(pp);

                } else {

                        Packet *pp = q_->tail();

                        q_->remove(pp);

                        drop(pp);

                }

        }

}

 

Packet* myfifo::deque()

{

        if (summarystats && &Scheduler::instance() != NULL) {

                Queue::updateStats(qib_?q_->byteLength():q_->length());

        }

        return q_->deque();

}

 

void myfifo::print_summarystats()

{

        //double now = Scheduler::instance().clock();

        printf("True average queue: %5.3f", true_ave_);

        if (qib_)

                printf(" (in bytes)");

        printf(" time: %5.3f\n", total_time_);

}

 

(4)    修改ns-default.tcl檔,設定初始內定值。

a.       cd  ns-allinone-2.27/ns-2.27/tcl/lib/

b.      使用文字編輯軟體打開ns-default.tcl

c.       使用搜尋的功能找到Queue/DropTail

d.      把每初始設定值都再設一份給Queue/myfifo

……………………………………………..

   Queue/DropTail set drop_front_ false

Queue/DropTail set summarystats_ false

Queue/DropTail set queue_in_bytes_ false

Queue/DropTail set mean_pktsize_ 500

 

Queue/myfifo set drop_front_ false

Queue/myfifo set summarystats_ false

Queue/myfifo set queue_in_bytes_ false

Queue/myfifo set mean_pktsize_ 500

……………………………………………..

 

(5)    修改Makefile,把myfifo.o加入OBJ_CC內,並重新編譯。

a.       使用文字編輯軟體打開ns-allinone-2.27/ns-2.27目錄下的Makefile

b.      使用搜尋找到drop-tail.o

c.       drop-tail.o後面加上queue/myfifo

……………………………………………..

tools/flowmon.o tools/loss-monitor.o \

queue/queue.o queue/drop-tail.o  queue/myfifo.o \

adc/simple-intserv-sched.o queue/red.o \

……………………………………………

d.      重新編譯。

make

 

        若是沒有什麼錯誤訊息產生,就是編譯成功。如下圖所示。

 

(6)    測試一下新安裝的模組是否可以正常的運作。可以把修改基本工具章節中的範例,把DropTail改成myfifo,試看跑出來的結果是否跟用DropTail跑出來的一樣。

 

在這邊先做個小結,如何新增模組到ns2的核心步驟如下:

1.準備好模組檔(例如,a.cc a.h)

2.若有需要做初始設定的話,修改ns-default.tcl檔。

3.修改Makefile(a.o加到OBJ_CC)

4.重新編譯

5.測試模組

 

關於如何去寫C++的模組檔案,並不包含在這份文件內。因為每模組的功能都不相同,但筆者可以給使用者一些建議,因為ns2內已經有很多類型的模組檔案,使用者可以根據不同的需要,找到最相近的模組檔,然後再去修改,以這樣的方式會比較容易把自己所需要的模組加入ns2的核心。

 

繼續新的主題前,筆者要告訴使用者在C++程式內非常重要的地方,第一個是bind相關函數,以前面的myfifo為例,在myfifo.h程式碼內:

class myfifo : public Queue {

  public:

        myfifo() {

                q_ = new PacketQueue;

                pq_ = q_;

                bind_bool("drop_front_", &drop_front_);

                bind_bool("summarystats_", &summarystats);

                bind_bool("queue_in_bytes_", &qib_);  // boolean: q in bytes?

                bind("mean_pktsize_", &mean_pktsize_);

                //              _RENAMED("drop-front_", "drop_front_");

        }

……………………………………………..

可以看到bind或者是bind_bool,這兩函數主要的目的是要把TCL的變數和C++的變數產生互連的關係,這樣使用者就可以在TCL檔內去設定或者去查看目前C++的變數值。(bind_bool是用在布林變數,bind是用在一般實數;而第一個參數是TCL的變數,第二參數是C++內的變數。) 筆者以在基本工具內的範例為例,教大家如何去改變或查看C++的變數值。

 

[範例:設定C++的參數值]

一般而言,FiFo queue的行為都是當佇列內的暫存區滿了以後,就會把新進的封包所丟棄,這是因為在ns-default.tcl初始設定檔內 drop_front_是設成false。但是如果想要改變成當暫存區滿了時候,要把佇列暫存區最前面的封包所丟棄,則可以在TCL內使用:

 

#n2n3之間的佇列為例

set qn2n3_ [[$ns link $n2 $n3] queue]

$qn2n3_ set drop_front_ true

 

[範例二:如何去查看即時C++的參數值]

筆者以基本工具篇的範例為例,教大家如何量測FTP那條TCP flowcongestion window變化。

# 產生一個模擬的物件

set ns [new Simulator]

 

#針對不同的資料流定義不同的顏色,這是要給NAM用的

$ns color 1 Blue

$ns color 2 Red

 

#開啟一個NAM trace file

set nf [open out.nam w]

$ns namtrace-all $nf

 

#開啟一個trace file,用來記錄封包傳送的過程

set nd [open out.tr w]

$ns trace-all $nd

 

#開啟一個檔案,用來記錄TCP Flowcongestion window變化情況

set wnd_trace [open cwnd.tr w]

 

#定義一個紀錄的程序,每隔0.1秒就去查看目前TCP Flow的值

proc record {} {

global ns tcp wnd_trace

set time 0.1

         

#讀取C++cwnd_的變數值

set curr_cwnd [$tcp set cwnd_]

    set now [$ns now]

    puts $wnd_trace "$now   $ curr_cwnd "

    $ns at [expr $now+$time] "record"

}      

 

#定義一個結束的程序

proc finish {} {

        global ns nf nd wnd_trace

        $ns flush-trace

        close $nf

        close $nd

        close $wnd_trace

        #以背景執行的方式去執行NAM

        #exec nam out.nam &

        exit 0

}

 

#產生四個網路節點

set n0 [$ns node]

set n1 [$ns node]

set n2 [$ns node]

set n3 [$ns node]

 

#把節點連接起來

$ns duplex-link $n0 $n2 2Mb 10ms DropTail

$ns duplex-link $n1 $n2 2Mb 10ms DropTail

$ns duplex-link $n2 $n3 1.7Mb 20ms DropTail

 

set qn2n3_ [[$ns link $n2 $n3] queue]

 

#設定ns2n3之間的Queue Size10個封包大小

$ns queue-limit $n2 $n3 10

 

#設定節點的位置,這是要給NAM用的

$ns duplex-link-op $n0 $n2 orient right-down

$ns duplex-link-op $n1 $n2 orient right-up

$ns duplex-link-op $n2 $n3 orient right

 

#觀測n2n3之間queue的變化,這是要給NAM用的

$ns duplex-link-op $n2 $n3 queuePos 0.5

 

#建立一條TCP的連線

set tcp [new Agent/TCP]

$tcp set class_ 2

$ns attach-agent $n0 $tcp

set sink [new Agent/TCPSink]

$ns attach-agent $n3 $sink

$ns connect $tcp $sink

#NAM中,TCP的連線會以藍色表示

$tcp set fid_ 1

 

#TCP連線之上建立FTP應用程式

set ftp [new Application/FTP]

$ftp attach-agent $tcp

$ftp set type_ FTP

 

#建立一條UDP的連線

set udp [new Agent/UDP]

$ns attach-agent $n1 $udp

set null [new Agent/Null]

$ns attach-agent $n3 $null

$ns connect $udp $null

#NAM中,UDP的連線會以紅色表示

$udp set fid_ 2

 

#UDP連線之上建立CBR應用程式

set cbr [new Application/Traffic/CBR]

$cbr attach-agent $udp

$cbr set type_ CBR

$cbr set packet_size_ 1000

$cbr set rate_ 1mb

$cbr set random_ false

 

#設定FTPCBR資料傳送開始和結束時間

$ns at 0.1 "$cbr start"

$ns at 1.0 "$ftp start"

$ns at 1.1 "record"

$ns at 4.0 "$ftp stop"

$ns at 4.5 "$cbr stop"

 

#結束TCP的連線(不一定需要寫下面的程式碼來實際結束連線)

$ns at 4.5 "$ns detach-agent $n0 $tcp ; $ns detach-agent $n3 $sink"

 

#在模擬環境中,5秒後去呼叫finish來結束模擬(這樣要注意模擬環境中

#5秒並不一定等於實際模擬的時間

$ns at 5.0 "finish"

 

#執行模擬

$ns run

 

然後使用gnuplot,設定環境變數

set xtics 1,0.2,4

set xrange [1:4]

plot “cwnd.tr” with lines

 

第二要注意的地方是在C++程式碼内的command程序,這個command程序可以讓TCL去呼叫C++的命令。

例如$ftp start$ftp stop中的startstop都是去呼叫C++模組內的命令。所以使用者若是在寫C++模組程式時,要提供一些程序給TCL用,就可以寫在command內。

 

最後,在結束這個章節前,筆者要跟大家做個經驗的分享。在筆者使用ns2去做研究的時候,經常會去用myfifo queue去做一些量測,例如去量測封包傳送到網路的時的時間、大小、或者是統計封包被丟棄的數量,雖然這些量測都可以使用awk去分析trace file而得到,但是要提醒大家,若是要模擬的資料太大的時候,去分析的時間也會很久,但若是直接在模組內去撰寫量測的程式,模擬結束後,所需要的資料就已經可以獲得了。