NS代码分析

来源:互联网 发布:java io写入 编辑:程序博客网 时间:2024/04/28 20:52

1.       NS的整体的实现
固定网络的仿真是通过下面三层合作来实现的。

Application这个层是实现数据流的层次。Agent这个层是实现所有各层协议的的层次。Node这个部分由多个分类器(Classifier)实现了所有接收数据包进行判断是否进行转发或接收到Agent的部分。Link实现了队列、时延、Agent、记录Trace等一系列的仿真问题。

2.      

TclObject

Handler

ParentNode

Process

NsObject

Node

Application

Connector

Classifier

Delay

Queue

Agent

Trace

AddressClassifier

图表 2 NS内部类的继承关系图

NS内部类的继承关系

 

此图为NS内部类的继承关系,可以看出一些类是由那些类继承来的,这样相同的属性调用的函数就可以很方便的找到出处。

3.       NS中函数调用的分层

在用gdb跟踪NS发送一个cbr数据包的过程可以看到一下顺序:

CBR_Traffic::start

TrafficGenerator::timeout

Application::send

UdpAgent::sendmsg

Classifier::recv

Connect::recv

Connect::send

Trace::recv       写一条数据包加入队列的记录

Connector::send

Queue::recv

DropTail::enque

DequeTrace::recv     写一条数据包弹出队列的记录

Connector::send

LinkDelay::recv      插入事件到scheduler

Scheduler::dispatch   

Scheduler::run

从上面的顺序可以看出,数据包在发送以后先通过应用层(Application::send)进行发送;然后通过Agent层(UdpAgent::sendmsg),UdpAgent是在初始化Agent的时候确定的。UdpAgent还有一个作用是生成相应的数据包;然后进入Node部分的分类器ClassifierClassifier::recv),通过find()函数返回下一跳地址。这个函数是通过读取Packet的内容得到下一跳地址的,返回给recv函数后调用node->recv()进入connector;经过connector::recvconnector::send后确定数据包可发,进入Trace::recv,记录这个数据包加入队列的记录;之后通过connector::send,进入Queue::recv函数,将数据包正式加入发送队列,再根据已经设定好的方法确定加入队列是否成功,是否要被丢弃;再调用DequeTrace::recv记录数据包弹出队列的记录;再通过connector::send进入LinkDelay::recv,先判断目的节点是否可达,根据不同的结果将事件写入scheduler,等待按序执行。

上述的过程只是一个数据包从生成到发送出去的过程,因为NS是一个根据一个一个离散事件调度执行的,后面的过程用gdb跟不进去。但可以看出,数据包是发送给下一跳节点,可知数据包是通过每个中间节点的。

4.       NS中主要函数的分析

4.1.     CBR数据源

开始从下面函数进入CBR数据源发送:

void CBR_Traffic::start()

{

        init();   //初始化

        running_ = 1;

        timeout();  //进入发送数据的循环

}

void TrafficGenerator::timeout()

{

        if (! running_)   //判断是否要发送数据

             return;

 

      /* send a packet */

      send(size_);    //发送一个设定好大小的数据包

      /* figure out when to send the next one */

      nextPkttime_ = next_interval(size_);  

      /* schedule it */

      if (nextPkttime_ > 0)

             timer_.resched(nextPkttime_);

      else

             running_ = 0;

}

4.2.     中间几个函数只是体现分层

void Application::send(int nbytes)

{

      agent_->sendmsg(nbytes);

}

void Agent::sendmsg(int /*sz*/, AppData* /*data*/, const char* /*flags*/)

{

      fprintf(stderr,

      "Agent::sendmsg(int, AppData*, const char*) not implemented/n");

      abort();

}

上述两个函数其实并没有什么实质的操作,只是这样可以看出其经过了应用层和Agent层。

4.3.     Classifier的函数

void Classifier::recv(Packet* p, Handler*h)

{

      NsObject* node = find(p);  //查找目的节点

      if (node == NULL) {  //只要返回了目的节点就调用节点的recv函数

             /*

              这个将被丢弃,不用记录在trace文件中

              */

             Packet::free(p);

             return;

      }

      node->recv(p,h); 

}

NsObject* Classifier::find(Packet* p)

{

      NsObject* node = NULL;

      int cl = classify(p);  //根据发送的packet的记录找到slot

      if (cl < 0 || cl >= nslot_ || (node = slot_[cl]) == 0) { //根据slot得到下一跳node

             if (default_target_)

                    return default_target_;

             /*

              不能将数据包发送出去,因为返回结果不是一个对象.

              */

             Tcl::instance().evalf("%s no-slot %ld", name(), cl);

             if (cl == TWICE) {

                    /*

                     * Try again.  Maybe callback patched up the table.

                     */

                    cl = classify(p);

                    if (cl < 0 || cl >= nslot_ || (node = slot_[cl]) == 0)

                           return (NULL);

             }

      }

      return (node); //返回给classifier::recv得到的node的值

}

这个地址分类器就是根据数据包的内容,通过偏移查找接收的节点,然后调用接收节点的recv函数。而find函数是根据数据包的内容得到slot的值从而查询出谁是接收方的node

4.4.     Connector的函数

void Connector::recv(Packet* p, Handler* h)

{

      send(p, h);

}

inline void send(Packet* p, Handler* h) { target_->recv(p, h); }

connectorrecvsend函数是一个接口。这个函数中最重要的是target_这个值,这个值的不同会不同的调用Trace::recvQueue::recvLinkDelay::recv等等,但是这个值在那确定还没有看出来。

4.5.     Queue的函数

void Queue::recv(Packet* p, Handler*)

{

      double now = Scheduler::instance().clock();

      enque(p);   //根据规定的规则加入队列

      if (!blocked_) {

             /*

              这里没有堵塞.  将一个数据包发送出去.

              我们执行一个附加的检查,因为这个队列可能丢弃这个数据包

              即使它前面是空的。  (例如, RED队列就可能发生.)

              */

             p = deque();

             if (p != 0) {

                    utilUpdate(last_change_, now, blocked_);

                    last_change_ = now;

                    blocked_ = 1;

                    target_->recv(p, &qh_);  //调用dequetrace

             }

      }

}

Queue::recv这个函数调用DropTail规则将数据包加入队列,然后判断是否堵塞,如果没有则发送一个数据包,之前判断是认定这个包是否要被发送出去。这里也使用了target_->recv(),这里调用的是DequeTrace::recv函数,将记录一个数据包出队列的记录。

4.6.     LinkDelay的函数

void LinkDelay::recv(Packet* p, Handler* h)

{

      double txt = txtime(p);

      Scheduler& s = Scheduler::instance();

      if (dynamic_) { //这个是动态链路的标志,判断这个值确定链路是否为动态链

             Event* e = (Event*)p;

             e->time_= txt + delay_;

             itq_->enque(p); // 用一个队列来储存数据包

             s.schedule(this, p, txt + delay_);

      } else if (avoidReordering_) {

             // 预防重新安排带宽或时延改变

            double now_ = Scheduler::instance().clock();

            if (txt + delay_ < latest_time_ - now_ && latest_time_ > 0) {

                   latest_time_+=txt;

                   s.schedule(target_, p, latest_time_ - now_ );//schedule里面加入事件

            } else {

                   latest_time_ = now_ + txt + delay_;

                   s.schedule(target_, p, txt + delay_); //schedule里面加入事件

            }

 

      } else {

             s.schedule(target_, p, txt + delay_); //schedule里面加入事件

      }

      s.schedule(h, &intr_, txt); //schedule里面加入事件

}

这个函数非常重要,这个是数据包最后离开这个节点的出口,由这个函数写一个事件加入schedule,在一定的时间后调用。

s.schedule(target_, p, txt)这个的含义是在txt的时间以后调用target_的事件,处理p这个数据包。

你可以通过这个链接引用该篇文章:http://naonaoruby.bokee.com/viewdiary.11857286.html

原创粉丝点击