ns-2与TCP拥塞模拟 之七 ns-2中自带的应用Ping解读
以ns-2中自带的应用Agent/Ping为例,描述一下如何编写一个“应用”。下面引用的是版本2.35中的代码,其他版本可能略有差异。以下是需要注意的地方:
(1)头文件.h中声明的类属性和方法;
(2)Tcl脚本的变量与C++类的变量的绑定;
(3)C++类中的command函数是Agent类与Tcl的接口(Tcl脚本的命令直接作用于该函数);
(4)C++类中的recv函数,是Agent类功能实现的关键,网络中对于数据包的分类、转发和处理的操作都是通过这个函数来实现的;
(5)TclClass的编写与修改,为Tcl脚本提供接口。
具体的实践过程可以按照以下4个主要步骤进行,举例如下。
(1)添加头文件ns-2.35/apps/ping.h。
/*
* File:Header File for a new 'Ping' Agent Class for the ns
* network simulator
*Author: Marc Greis (greis@cs.uni-bonn.de), May 1998
*/
#ifndef ns_ping_h
#define ns_ping_h
#include "agent.h"
#include "tclcl.h"
#include "packet.h"
#include "address.h"
#include "ip.h"
struct hdr_ping {
charret; //从源端出来时值为 0,从目的端回来时值为 1;
doublesend_time; //源端发送的时间戳,用于往返时延的计算;
doublercv_time; //接收时刻
intseq; //序列号
//Header access methods
staticint offset_; //偏移量
inlinestatic int& offset() { return offset_; }
inlinestatic hdr_ping* access(const Packet* p) {
return(hdr_ping*) p->access(offset_);
}
};
class PingAgent : public Agent {
public:
PingAgent();
intseq; //发送序号
intoneway; //标志位
virtualint command(int argc, const char*const* argv);
virtualvoid recv(Packet*, Handler*);
};
#endif // ns_ping_h
(2)添加代码文件ns-2.35/apps/ping.cc。
/*
* File:Code for a new 'Ping' Agent Class for the ns
* network simulator
*Author: Marc Greis (greis@cs.uni-bonn.de), May 1998
*/
#include "ping.h"
int hdr_ping::offset_;
//以下的两个静态类定义主要完成C++和OTcl的连接
static class PingHeaderClass : publicPacketHeaderClass {
public:
PingHeaderClass(): PacketHeaderClass("PacketHeader/Ping",
sizeof(hdr_ping)) {
bind_offset(&hdr_ping::offset_);
}
} class_pinghdr;
static class PingClass : public TclClass {
public:
PingClass(): TclClass("Agent/Ping") {}
TclObject*create(int, const char*const*) {
return(new PingAgent());
}
} class_ping;
PingAgent::PingAgent() : Agent(PT_PING),seq(0), oneway(0) //PingAgent的构造函数
{
bind("packetSize_",&size_);
}
int PingAgent::command(int argc, constchar*const* argv)
{
if(argc == 2) {
if(strcmp(argv[1], "send") == 0) { //如:$p0 send,作为command 函数的输入
//Create a new packet
Packet* pkt = allocpkt();
//Access the Ping header for the new packet:
hdr_ping* hdr = hdr_ping::access(pkt);
//Set the 'ret' field to 0, so the receiving node
//knows that it has to generate an echo packet
hdr->ret = 0;
hdr->seq = seq++;
// Store thecurrent time in the 'send_time' field
hdr->send_time = Scheduler::instance().clock();
//Send the packet
send(pkt, 0);
//return TCL_OK, so the calling function knows that
//the command has been processed
return (TCL_OK);
}
elseif (strcmp(argv[1], "start-WL-brdcast") == 0) {
Packet* pkt = allocpkt();
hdr_ip* iph = HDR_IP(pkt);
hdr_ping* ph = hdr_ping::access(pkt);
iph->daddr() = IP_BROADCAST;
iph->dport() = iph->sport();
ph->ret = 0;
send(pkt, (Handler*) 0);
return (TCL_OK);
}
elseif (strcmp(argv[1], "oneway") == 0) {
oneway=1;
return (TCL_OK);
}
}
// Ifthe command hasn't been processed by PingAgent()::command,
//call the command() function for the base class
return(Agent::command(argc, argv));
}
void PingAgent::recv(Packet* pkt, Handler*)
{
//Access the IP header for the received packet:
hdr_ip*hdrip = hdr_ip::access(pkt);
//Access the Ping header for the received packet:
hdr_ping* hdr = hdr_ping::access(pkt);
//check if in brdcast mode
if((u_int32_t)hdrip->daddr() == IP_BROADCAST) {
if(hdr->ret == 0) {
printf("Recv BRDCAST Ping REQ : at %d.%d from %d.%d\n",here_.addr_, here_.port_, hdrip->saddr(), hdrip->sport());
Packet::free(pkt);
//create reply
Packet* pktret = allocpkt();
hdr_ping* hdrret = hdr_ping::access(pktret);
hdr_ip* ipret = hdr_ip::access(pktret);
hdrret->ret = 1;
//add brdcast address
ipret->daddr() = IP_BROADCAST;
ipret->dport() = ipret->sport();
send(pktret, 0);
} else {
printf("Recv BRDCAST Ping REPLY : at %d.%d from %d.%d\n",here_.addr_, here_.port_, hdrip->saddr(), hdrip->sport());
Packet::free(pkt);
}
return;
}
// Isthe 'ret' field = 0 (i.e. the receiving node is being pinged)?
if(hdr->ret == 0) {
//Send an 'echo'. First save the old packet's send_time
double stime = hdr->send_time;
intrcv_seq = hdr->seq;
//Discard the packet
Packet::free(pkt);
//Create a new packet
Packet* pktret = allocpkt();
//Access the Ping header for the new packet:
hdr_ping* hdrret = hdr_ping::access(pktret);
//Set the 'ret' field to 1, so the receiver won't send
//another echo
hdrret->ret = 1;
//Set the send_time field to the correct value
hdrret->send_time = stime;
//Added by Andrei Gurtov for one-way delay measurement.
hdrret->rcv_time = Scheduler::instance().clock();
hdrret->seq = rcv_seq;
//Send the packet
send(pktret, 0);
} else{
// Apacket was received. Use tcl.eval to call the Tcl
//interpreter with the ping results.
//Note: In the Tcl code, a procedure
//'Agent/Ping recv {from rtt}' has to be defined which
//allows the user to react to the ping result.
charout[100];
//Prepare the output to the Tcl interpreter. Calculate the
//round trip time
if(oneway) //AG
sprintf(out,"%s recv %d %d %3.1f %3.1f", name(),
hdrip->src_.addr_ >>Address::instance().NodeShift_[1],
hdr->seq, (hdr->rcv_time -hdr->send_time) * 1000,
(Scheduler::instance().clock()-hdr->rcv_time)* 1000);
elsesprintf(out, "%s recv %d %3.1f",name(),
hdrip->src_.addr_ >>Address::instance().NodeShift_[1],
(Scheduler::instance().clock()-hdr->send_time)* 1000);
Tcl& tcl = Tcl::instance();
tcl.eval(out);
//Discard the packet
Packet::free(pkt);
}
}
以上代码的逻辑比较清晰明了,可以看出,比较重要的是command函数中处理“send”命令的部分和recv函数。
① 函数command中处理“send”命令的部分,在逻辑上是发送端,发送第一个报文分组。
② 函数recv中结构“if(hdr->ret == 0) {…}”部分,逻辑上是接收端处理收到的分组;填充了一个新的报文分组的标志、时间戳、序号等部分后,发送了这个响应包;当然,这个recv函数比较复杂的部分是考虑了广播包和双向反馈。
(3)ns-2中应该做的其他必要修改。
① 修改ns-2.35/common/packet.h文件。
…
// RAP packets
static const packet_t PT_RAP_DATA = 40;
static const packet_t PT_RAP_ACK = 41;
static const packet_t PT_TFRC = 42;
static const packet_t PT_TFRC_ACK = 43;
static const packet_t PT_PING = 44;
static const packet_t PT_PBC = 45;
// Diffusion packets - Chalermek
static const packet_t PT_DIFF = 46;
…
class p_info {
…
staticvoid initName()
{
if(nPkt_>= PT_NTYPE+1)
return;
…
name_[PT_TFRC]="tcpFriend";
name_[PT_TFRC_ACK]="tcpFriendCtl";
name_[PT_PING]="ping";
name_[PT_PBC]= "PBC";
…
}
}
② 修改ns-2.35/tcl/lib/ns-default.tcl文件。
…
##Agent set seqno_ 0 now is gone
##Agent set class_ 0 now is gone
Agent/Ping set packetSize_ 64
Agent/UDP set packetSize_ 1000
Agent/UDP instproc done {} { }
Agent/UDP instproc process_data {from data} {}
…
③ 修改ns-2.35/tcl/lib/ns-packet.tcl文件。
set protolist {
…
# Application-Layer Protocols:
Message # a protocol to carry text messages
Ping # Ping
PBC # PBC
…
}
④ 修改ns-2.35/Makefile文件。
OBJ_CC = \
tools/random.otools/rng.o tools/ranvar.o common/misc.o common/timer-handler.o \
…
tcp/tfrc.otcp/tfrc-sink.o mobile/energy-model.o apps/ping.o tcp/tcp-rfc793edu.o \
…
执行make 命令。
(4)运行以下ping.tcl 进行测试。
#ping.tcl 文件
#Create a simulator object
set ns [new Simulator]
#Open a trace file
set tracefd [open out.tr w]
$ns trace-all $tracefd
set nf [open out.nam w]
$ns namtrace-all $nf
#Define a 'finish' procedure
proc finish {} {
global ns nf
$ns flush-trace
close $nf
exec nam out.nam &
exit 0
}
#Create three nodes
set n0 [$ns node]
set n1 [$ns node]
set n2 [$ns node]
#Connect the nodes with two links
$ns duplex-link $n0 $n1 1Mb 10ms DropTail
$ns duplex-link $n1 $n2 1Mb 10ms DropTail
#Create two ping agents and attach them to thenodes n0 and n2
set p0 [new Agent/Ping]
$ns attach-agent $n0 $p0
set p1 [new Agent/Ping]
$ns attach-agent $n2 $p1
#Connect the two agents
$ns connect $p0 $p1
#Schedule events
$ns at 0.2 "$p0send"
$ns at 0.4 "$p1send"
$ns at 0.6 "$p0send"
$ns at 0.6 "$p1send"
$ns at 1.0 "finish"
#Run the simulation
$ns run
整体上来看,与代理模块相比,添加一个新的“应用”,需要在合适的Tcl文件中添加相应的报文类型定义。