今天我们开始讲解一篇这个Ns tutorial中介绍的如何完成添加一个ping协议的过程。
在本节中,我将举例说明可以在ns中实现的新协议。在你自己尝试之前,你应该对ns非常熟悉,并且一些C ++知识肯定是必要的。您还应该至少阅读“ns Notes and Documentation”(现在重命名为ns Manual)中的第3.1-3.3章,以了解Tcl和C ++之间的交互。
本节中的代码实现了某种简单的“ping”协议(受“ns注释和文档”(现在更名为ns Manual)第9.6章中的“ping请求者”的启发,但相当不同)。一个节点将能够将数据包发送到另一个节点,该节点将立即返回它,以便可以计算往返时间。
我知道这里提供的代码可能不是最好的实现,我相信它可以改进,但我希望它很容易理解,这是这里的主要优先事项。但是,可以在此处发送建议。
1.头文件
在新的头文件’ping.h’中,我们首先要声明新的Ping包头的数据结构,它将携带相关数据。
struct hdr_ping {
char ret;
double send_time;
};
如果数据包正在从发送方到正在被ping的节点的路上,则char’ret’将被设置为’0’,而在返回途中它将被设置为’1’。双“send_time”是在发送数据包时在数据包上设置的时间戳,稍后用于计算往返时间。 下面的代码将“PingAgent”类声明为“Agent”类的子类。
class PingAgent : public Agent {
public:
PingAgent();
int command(int argc, const char*const* argv);
void recv(Packet*, Handler*);
protected:
int off_ping_;
};
在下一节中,我将介绍用于定义构造函数’PingAgent()'的C ++代码以及在此声明中重新定义的函数’command()‘和’recv()’。 int’off_ping_'将用于访问数据包的ping头。请注意,对于具有本地对象范围的变量,通常使用尾随的“_”。 你可以在这里下载完整的头文件(我建议你这样做并快速查看它,因为这里提供的代码并不完全)。
2.c++ 代码
首先,必须定义C ++代码和Tcl代码之间的链接。您没有必要完全理解这段代码,但如果您还没有完全理解它,它将帮助您阅读“ns手册”中的章节3.1-3.3。
static class PingHeaderClass : public PacketHeaderClass {
public:
PingHeaderClass() : PacketHeaderClass("PacketHeader/Ping",
sizeof(hdr_ping)) {}
} class_pinghdr;
static class PingClass : public TclClass {
public:
PingClass() : TclClass("Agent/Ping") {}
TclObject* create(int, const char*const*) {
return (new PingAgent());
}
} class_ping;
下一段代码是“PingAgent”类的构造函数。它绑定了必须在Tcl和C ++中访问的变量。
PingAgent::PingAgent() : Agent(PT_PING)
{
bind("packetSize_", &size_);
bind("off_ping_", &off_ping_);
}
执行“PingAgent”类的Tcl命令时,将调用函数’command()’。在我们的情况下,将是’$ pa send’(假设’pa’是Agent / Ping类的实例),因为我们希望将ping数据包从代理发送到另一个ping代理。你基本上必须在’command()‘函数中解析命令,如果没有找到匹配,你必须将带有参数的命令传递给基类的’command()‘函数(在本例中为’Agent’) ::命令()’)。代码可能看起来很长,因为它的评论很多。
int PingAgent::command(int argc, const char*const* argv)
{
if (argc == 2) {
if (strcmp(argv[1], "send") == 0) {
// Create a new packet
Packet* pkt = allocpkt();
// Access the Ping header for the new packet:
hdr_ping* hdr = (hdr_ping*)pkt->access(off_ping_);
// Set the 'ret' field to 0, so the receiving node knows
// that it has to generate an echo packet
hdr->ret = 0;
// Store the current 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);
}
}
// If the command hasn't been processed by PingAgent()::command,
// call the command() function for the base class
return (Agent::command(argc, argv));
}
函数’recv()'定义了接收数据包时要采取的操作。如果’ret’字段为0,则必须返回与’send_time’字段具有相同值但是’ret’字段设置为1的数据包。如果’ret’为1,则调用Tcl函数(必须由用户在Tcl中定义)并处理该事件(对于版本2.1b2的用户的重要说明:'Address :: instance()。NodeShift_ [ 1]'必须替换为’NODESHIFT’以使示例在ns 2.1b2下工作)。
void PingAgent::recv(Packet* pkt, Handler*)
{
// Access the IP header for the received packet:
hdr_ip* hdrip = (hdr_ip*)pkt->access(off_ip_);
// Access the Ping header for the received packet:
hdr_ping* hdr = (hdr_ping*)pkt->access(off_ping_);
// Is the '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;
// 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*)pktret->access(off_ping_);
// 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;
// Send the packet
send(pktret, 0);
} else {
// A packet 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.
char out[100];
// Prepare the output to the Tcl interpreter. Calculate the round
// trip time
sprintf(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);
}
}
最有趣的部分应该是’tcl.eval()‘函数,其中调用Tcl函数’recv’,其中pinged节点的id和往返时间(以毫秒为单位)作为参数。第VII.4节将显示如何编写此函数的代码。但首先,在重新编译ns之前,必须编辑其他一些文件。
3.必要的改变
如果要添加新代理,则必须更改某些ns源文件中的某些内容,尤其是在使用新数据包格式时。我建议您始终使用注释标记更改,使用#ifdef等,以便您可以轻松删除更改或将其移植到新的ns版本。
我们将需要为ping代理程序使用新的数据包类型,因此第一步是编辑文件’packet.h’。在那里,您可以找到数据包协议ID的定义(即PT_TCP,PT_TELNET等)。在那里为PT_PING添加新定义。在我编辑的packet.h版本中,enum packet_t {}的最后几行看起来像下面的代码(在早期版本或更高版本中可能看起来有点不同)。
enum packet_t {
PT_TCP,
PT_UDP,
......
// insert new packet types here
PT_TFRC,
PT_TFRC_ACK,
PT_PING, // packet protocol ID for our ping-agent
PT_NTYPE // This MUST be the LAST one
};
您还必须编辑同一文件中的p_info()以包含“Ping”。
class p_info {
public:
p_info() {
name_[PT_TCP]= "tcp";
name_[PT_UDP]= "udp";
...........
name_[PT_TFRC]= "tcpFriend";
name_[PT_TFRC_ACK]= "tcpFriendCtl";
name_[PT_PING]="Ping";
name_[PT_NTYPE]= "undefined";
}
.....
}
请记住,在执行’make’之前必须先执行’make depend’,否则可能无法重新编译这两个文件。 还必须编辑文件’tcl / lib / ns-default.tcl’。这是定义Tcl对象的所有默认值的文件。插入以下行以设置代理/ Ping的默认数据包大小。
Agent/Ping set packetSize_ 64
您还必须在文件开头的列表中的文件’tcl / lib / ns-packet.tcl’中为新的ping数据包添加一个条目。它看起来像下面的代码。
{ SRMEXT off_srm_ext_}
{ Ping off_ping_ }} {
set cl PacketHeader/[lindex $pair 0]
最后一个更改是必须应用于’Makefile’的更改。您必须将文件’ping.o’添加到ns的目标文件列表中。在我的版本中,编辑列表的最后几行如下所示:
sessionhelper.o delaymodel.o srm-ssm.o \
srm-topo.o \
ping.o \
$(LIB_DIR)int.Vec.o $(LIB_DIR)int.RVec.o \
$(LIB_DIR)dmalloc_support.o \
您现在应该可以通过在ns目录中键入“make”来重新编译ns。如果您遇到任何问题,请发送电子邮件至ns-users。
4.tcl 代码
我现在不打算为Ping代理提供Tcl示例的完整代码。您可以在此处下载完整示例。但是我将向您展示如何在收到ping’echo’数据包时编写从C ++代码中的’recv()'函数调用的’recv’过程。
Agent/Ping instproc recv {from rtt} {
$self instvar node_
puts "node [$node_ id] received ping answer from \
$from with round-trip-time $rtt ms."
}
这段代码应该很容易理解。唯一新的事情是它访问基类’Agent’的成员变量’node_‘以获取代理所附加的节点的节点id。 现在您可以尝试自己的一些实验。一个非常简单的实验是不将数据包中的’ret’字段设置为1.您可以猜测会发生什么。您还可以尝试添加一些代码,允许用户使用’$ pa send $ node’发送ping数据包(其中’pa’是ping代理,'节点’是节点),而无需将代理’pa’与首先是’node’上的ping代理,虽然这可能比起初听起来有点复杂。您还可以阅读“ns手册”中的第9.6章,以了解有关创建自己的代理的更多信息。祝你好运。