NS 发送一个 cbr 数据包的过程

NS 发送一个 cbr 数据包的过程

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 部分的分类器 Classifier Classifier::recv),通过 find ()函数返回下一跳地址。这个函数是通过读取 Packet 的内容得到下一跳地址的,返回给 recv 函数后调用 node->recv()进入 connector ;经过 connector::recv connector::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();  // 进入发送数据的循环

}

voidTrafficGenerator::timeout()

{

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

            return;

 

     

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

     

     nextPkttime_ =next_interval(size_);  

     

     if (nextPkttime_ > 0)

            timer_.resched(nextPkttime_);

     else

            running_ = 0;

}

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

void Application::send(intnbytes)

{

     agent_->sendmsg(nbytes);

}

void Agent::sendmsg(int , AppData* , const char*)

{

     fprintf(stderr,

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

     abort();

}

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

4.3.     Classifier的函数

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

{

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

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

            

            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) {

                   

                   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); }

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

4.5.     Queue 的函数

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

{

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

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

     if (!blocked_) {

            

            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 这个数据包。

你可能感兴趣的:(ns2)