转自:http://blog.csdn.net/geekster/article/details/5564660
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内部类的继承关系,可以看出一些类是由那些类继承来的,这样相同的属性调用的函数就可以很方便的找到出处。
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(); //进入发送数据的循环
}
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); }
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_) {
/*
* 这里没有堵塞. 将一个数据包发送出去.
* 我们执行一个附加的检查,因为这个队列可能丢弃这个数据包
* 即使它前面是空的。 (例如, 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