http://www.isi.edu/nsnam/ns/tutorial/跟着这个添加自己的ping协议,环境是Ubuntu10.04, NS2.34,其实这个版本的NS已经实现的ping协议,并且与前面网站版本不同,在改代码的时候会有所不同。首先添加自己的ping.h头文件:
1 #ifndef ns_ping_h
2 #define ns_ping_h
3
4 #include "agent.h"
5 #include "tclcl.h"
6 #include "packet.h"
7 #include "address.h"
8 #include "ip.h"
9
10 struct hdr_ping {
11 //set '0' if the packet is on its way from the sender to the node which is being pinged,while it is going to
12 //be set to '1'on its way back.
13 char ret;
14 //a time stamp that is set on the packet when it is sent.
15 double send_time;
16 }
17
18 class PingAgent : public Agent {
19 public :
20 PingAgent();
21 int command(int argc, const char * const * argv);
22 void recv(Packet *, Handler *);
23 protected :
24 int off_ping_;
25 }
26 #endif
注意为了不影响已经存在的分组头,最好是自己添加命名规则一般是hdr_new。
1 static class PingHeaderClass : public PacketHeaderClass {
2 public :
3 PingHeaderClass() : PacketHeaderClass("PacketHeader/Ping",
4 sizeof(hdr_ping)) {}
5 }class_pinghdr;
6
7 static class PingClass : public TclClass {
8 public :
9 PingClass() : TclClass("Agent/Ping") {}
10 TclObject *create(int, const char * const *) {
11 return (new PingAgent());
12 }
13 }class_ping;
14
15 PingAgent :: PingAgent() : Agent(PT_PING)
16 {
17 bind("packetSize_", &size);
18 bind("off_ping_", &off_ping_);
19 }
20
21 int PingAgent :: command(int argc, const *char *const argv)
22 {
23 if(argc == 2) {
24 if(strcmp(argv[1], "send") == 0) {
25 //create a new packet
26 Packet* pkt = allocpkt();
27 //access the ping header for tne new packet
28 hdr_ping* hdr = (hdr_ping*)pkt->access(off_ping_);
29 //set the 'ret' field to 0, so the receiving node knows
30 //that it has to generate an echo packet
31 hdr->ret = 0;
32 //store the current time in the 'send_time' field
33 hdr->send_time = Schedulet::instance.clock();
34 //send the packet
35 send(pkt, 0);
36 //return TCL_OK, so the calling function knows that
37 //the command has been processed
38 return TCL_OK;
39 }
40 }
41
42 //if the command hasn't been processed by PingAgent()::command,
43 //call the command() function for the base class
44 return (Agent::command(argc, argv));
45 }
46
47
48 void PingAgent :: recv(Pakcet *pkt, Handler *)
49 {
50 //aceess the IP header for the recived packet:
51 hdr_ip * hdrip = (hdr_ip*)pkt->access(off_ip_);
52 //access the ping header for the recived packet:
53 hdr_ping * hdr = (hdr_ping*)pkt->access(off_ping_);
54
55 if(hdr->ret == 0) {
56 //send an 'echo'.First save the old packet's send_time/
57 double stime = hdr->send_time;
58 //discard the packet
59 Packet::free(pkt);
60 //create a new packet
61 Packet * pktret = allocpkt();
62 //access the ping header for the new packet
63 hdr_ping* hdrret = (hdr_ping*)pktret->access(off_ping_);
64 //set the 'ret' field to 1, so the receiver won't send another echo
65 hdrret->ret = 1;
66 //set the send time field to the correct value
67 hdrret->send_time = stime;
68 //send the packet
69 send(pktret, 0);
70 } else {
71 //a packet was received. Use tcl.eval to call the Tcl
72 //interreter with the ping results.
73 //note : In the Tcl code, a procedure 'Agent/Ping recv {form rtt}'
74 //has to be defined which allows the user to react to the ping
75 //result.
76 char out[100];
77 //prepare the output to the Tcl interpreter. Calculate the round
78 //trip time.
79 sprintf(out, "%s recv %d %3.1f", name(),
80 hdrip->src_.addr_ >> Address::instance().NodeShitf_[1],
81 (Scheduler::instance().clock()-hdr->send_time) * 1000);
82 Tcl& tcl = Tcl::instance();
83 tcl.eval(out);
84 //discard the packet
85 Packet::free(pkt);
86 }
87 }
有4个C++类与分组头和分组有关,它们分别是Packet, p_info, PacketHeaderClass和PacketHeaderManager.其中 PacketHeaderClass是各种分组头的基类,而它本身是继承于TclClass的,现在对PingHeaderClass的作用理解不是很清楚,而PingClass用于创建一个PingAgent对象,注意关键字static,这样保证了当NS2启动时能调用create返回一个TclObject对象(NS2的分裂对象模型),PingAgent的构造函数绑定了两个变量。command方法牵涉到cmd机制,cmd命令是meet the Tcl Unknown mechanism——Tcl的unknown机制,简单来说,当向ns输入一个未知的命令时,Tcl的unknow机制就会调用cmd函数,cmd函数激活shadow对象的command方法,进而执行这个command方法,在这里我们在command中添加了send命令。在这里我们并没有重写父类Agent的send方法,可以在~ns/common/agent.{cc, h}中看到send的实现:
1 void send(Packet* p, Handler* h) { target_->recv(p, h); }
所以,send方法调用了接受者的recv方法,而我们实现了recv方法,并调用tcl对象的recv方法,这就是为什么在下面的tcl文件中recv方法被调用的理由。
当然,还需要一些必要的改变:
首先要增加分组头类型,在NS2.34中,修改了packet_t的类型,在ns2.33之前packet_t是enum,而之后定义为unsigned int.所以添加如下:
1 static const packet_t PT_PING = 44;
需要注意的是,ns2.34已经实现了ping协议,所以我并没有添加任何内容,如果是自己另写协议,在添加packet_t时,必须注释掉不用的分组头,并且要按所有常量值的顺序存储,不然就会有如下错误:
1 diffserv -I./satellite -I./wpan -I./mit/rca -I./mit/uAMPS -o common/ptypes2tcl.o common/ptypes2tcl.cc
2 g++ -Wl,-export-dynamic -o common/ptypes2tcl common/ptypes2tcl.o
3 ./common/ptypes2tcl > gen/ptypes.cc
4 Segmentation fault
5 make: *** [gen/ptypes.cc] Error 139
PS:还需要该/bin目录的权限:
chmod -R 755 /bin
不然会有权限问题,还得用sudo make。
在p_info类中添加内容时也可网站不同:
1 p_info()
2 {
3 initName();
4 }
所以需要在initName()方法中添加名字。另外,需要在~ns/tcl/lib/ns-default.tcl中添加包的大小:
1 Agent/Ping set packetSize_ 64
然后在~ns/tcl/lib/ns-packet.tcl的foreach prot模块中添加:
1 Ping # Ping
最后在makefile文件中添加,直接复制源码吧(自己以后注意添加就是了):
1 tcp/tfrc.o tcp/tfrc-sink.o mobile/energy-model.o apps/ping.o tcp/tcp-rfc793edu.o
可以看到,ns自带的ping源码在apps/目录下。
再加上tcl文件:
1 #create a simulator object
2 set ns [new Simulator]
3
4 #open a trace file
5 set tracefd [open out.tr w]
6 $ns trace-all $tracefd
7 set nf [open out.nam w]
8 $ns namtrace-all $nf
9
10 #define a finish procedure
11 proc finish {} {
12 global ns nf
13 $ns flush-trace
14 close $nf
15 exec nam out.nam &
16 exit 0
17 }
18
19 #create three nodes
20 set n0 [$ns node]
21 set n1 [$ns node]
22 set n2 [$ns node]
23
24 #connect the nodes with two links
25 $ns duplex-link $n0 $n1 1Mb 10ms DropTail
26 $ns duplex-link $n1 $n2 1Mb 10ms DropTail
27
28 #define a recv funciton for the class Agent/Ping
29 Agent/Ping instproc recv {from rtt} {
30 $self instvar node_
31 puts "node [$node_ id] received ping answer from/
32 $from with round-trip-time $rtt ms."
33 }
34
35 #create two ping agents and attach them to the nodes n0 and n2
36 set p0 [new Agent/Ping]
37 $ns attach-agent $n0 $p0
38
39 set p1 [new Agent/Ping]
40 $ns attach-agent $n2 $p1
41
42 #connect
43 $ns connect $p0 $p1
44
45 $ns at 0.2 "$p0 send"
46 $ns at 0.4 "$p1 send"
47 $ns at 0.6 "$p0 send"
48 $ns at 0.6 "$p1 send"
49 $ns at 1.0 "finish"
50
51 $ns run
可以看到在tcl文件中,调用了send命令,并且添加了tcl对象的recv方法,方便c++shadow对象调用。需要注意的是,connect方法并没有建立连接,只是告知目的地址,并且在ns中,应用层之间并没有发送数据,都是由代理模拟的。
疑问:对NS2的内部运行机制还是半懂不懂,并且不理解单线程是怎么实现多节点同时发送数据的,另外,对PacketHeaderManager类的作用也还没有体会到。