原文地址 :http://140.116.72.80/~smallko/ns2/module.htm
這個章節最主要的目的就是希望使用者可以學會如何新增或修改ns2的核心模組,更明確的說就是去新增和修改[*.cc, *.h]檔案,以筆者和筆者朋友學習ns2的經驗來說,這需要花很多時間和很大的勇氣,時間是花來找資料,勇氣是用來承受當程式寫不好時,ns2可能隨時會當掉。不過別怕,只要跟著筆者所介紹的方法,一定可以成功,若是不成功,就寫信給筆者吧。
若是一開始就要叫ns2的新手去增加的模組,新手一定會哇哇叫,所以筆者不會這樣做,筆者先教大家拷貝ns2已經有的模組檔,但改成別的名字,這樣就可以輕鬆且很明確知道該如何增加模組。
<!--[if !supportLists]-->(1) <!--[endif]-->打開cygwin的命令視窗,把路徑切換到queue的目錄下。
cd ns-allinone-2.27/ns-2.27/queue
<!--[if !supportLists]-->(2) <!--[endif]-->拷貝drop-tail.[cc, h]到myfifo.[cc.h]。
cp drop-tail.cc myfifo.cc
cp drop-tail.h myfifo.h
<!--[if !supportLists]-->(3) <!--[endif]-->使用文字編輯軟體去修改myfifo.h和myfifo.cc。(因為我們的環境是在windows下,所以建議可以使用ultra-edit來修改。
<!--[if !supportLists]-->a. <!--[endif]-->先修改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
<!--[if !supportLists]-->b. <!--[endif]-->再修改myfifo.cc,使用取代的功能把所有DropTail改成myfifo,另外,把drop_tail改成myfifo和drop-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_);
}
<!--[if !supportLists]-->(4) <!--[endif]-->修改ns-default.tcl檔,設定初始內定值。
<!--[if !supportLists]-->a. <!--[endif]-->cd ns-allinone-2.27/ns-2.27/tcl/lib/
<!--[if !supportLists]-->b. <!--[endif]-->使用文字編輯軟體打開ns-default.tcl
<!--[if !supportLists]-->c. <!--[endif]-->使用搜尋的功能找到Queue/DropTail
<!--[if !supportLists]-->d. <!--[endif]-->把每個初始設定值都再設一份給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
……………………………………………..
<!--[if !supportLists]-->(5) <!--[endif]-->修改Makefile,把myfifo.o加入OBJ_CC內,並重新編譯。
<!--[if !supportLists]-->a. <!--[endif]-->使用文字編輯軟體打開ns-allinone-2.27/ns-2.27目錄下的Makefile
<!--[if !supportLists]-->b. <!--[endif]-->使用搜尋找到drop-tail.o。
<!--[if !supportLists]-->c. <!--[endif]-->在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 /
……………………………………………
<!--[if !supportLists]-->d. <!--[endif]-->重新編譯。
make
若是沒有什麼錯誤訊息產生,就是編譯成功。如下圖所示。
測試一下新安裝的模組是否可以正常的運作。可以把修改基本工具章節中的範例,把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內使用:
#以n2到n3之間的的佇列為例
set qn2n3_ [[$ns link $n2 $n3] queue]
$qn2n3_ set drop_front_ true
[範例二:如何去查看即時C++的參數值]
筆者以基本工具篇的範例為例,教大家如何量測FTP那條TCP flow的congestion 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 Flow的congestion 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]
#設定ns2到n3之間的Queue Size為10個封包大小
$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
#觀測n2到n3之間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
#設定FTP和CBR資料傳送開始和結束時間
$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中的”start”和”stop”都是去呼叫C++模組內的命令。所以使用者若是在寫C++模組程式時,要提供一些程序給TCL用,就可以寫在command內。
最後,在結束這個章節前,筆者要跟大家做個經驗的分享。在筆者使用ns2去做研究的時候,經常會去用myfifo queue去做一些量測,例如去量測封包傳送到網路的時的時間、大小、或者是統計封包被丟棄的數量,雖然這些量測都可以使用awk去分析trace file而得到,但是要提醒大家,若是要模擬的資料太大的時候,去分析的時間也會很久,但若是直接在模組內去撰寫量測的程式,模擬結束後,所需要的資料就已經可以獲得了。