**

NS改进协议(1)

**

今天我们开始讲解一篇这个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章,以了解有关创建自己的代理的更多信息。祝你好运。
10-06 18:11