关于NS3的事件调度机制的理解

ns-3是一个离散事件网络模拟器。各个事件都会有个指定执行的预定模拟时间。从概念上讲,模拟器会跟踪预定好模拟时间待执行的多个事件。模拟器的工作机制是按预定模拟时间顺序执行事件,依次调度。一旦事件发生并执行完成,模拟器将移动到下一个事件执行。

要做到上述的一切,模拟器需要有以下一些要素:

(1)模拟器对象,该对象可以访问事件队列,并可以管理事件的执行。

(2)调度程序,负责插入和删除队列中的事件。

(3)一种表示模拟时间的方式。

(4)事件本身,通常是某个函数。

产生调度事件的方式有两种,即需要和不需要context,这里的context包含的是事件对应的节点的信息。如果要调度的事件仍是由该节点执行的,则不需要context。而如果是产生跨节点的调度事件的,则需要给调度器传递context即节点的信息。跨节点的调度事件的这种情况通常发生在发送包被传递到信道,要使信道中其它节点能接收到该数据包,则在调度接收事件时还要传递目标节点的context信息。

这两种调度方式分别是:

Simulator::Schedule (Time const &time, MEM mem_ptr, OBJ obj);

Simulator::ScheduleWithContext (uint32_t context, Time const &time, MEM mem_ptr, OBJ obj);

其中参数time是事件的预定执行时间,参数mem_ptr是被调度的事件的指针,参数obj是发起该调度的类对象的指针。

ns3的脚本在设置应用层的时候,实际上就已经产生了一个发包事件。一个应用层的发包事件是一个接一个的,一个发包事件结束后,将根据应用层的实际情况,计算时间间隔,以此时间间隔产生下一次的发包事件。产生的发包事件会被添加到事件队列。

在仿真脚本的最后,往往会有以下代码:

Simulator::Stop (Seconds (21.0));
Simulator::Run ();
Simulator::Destroy ();

      第一句是预先设定仿真结束时间,如果没有这句,仿真运行将会永远停留在Simulator::Run ()中。

Simulator::Run ()相当于一个循环,该循环会不断检测事件队列的待执行事件,把预定时间最先的(先入列不一定先执行,要看事件的执行时间)的事件出列,执行完毕,再执行下一个。在非realtime仿真模式下,模拟器不关心真正的仿真时间,只会按照事件的发生顺序一个个执行,仿真时间被压缩,仿真过程就很快执行完成。在realtime模式下,事件的执行时间就会严格与设定的时间一致,仿真过程就会比较慢。实时化仿真代码如下:

GlobalValue::Bind ("SimulatorImplementationType",
                     StringValue ("ns3::RealtimeSimulatorImpl"));

Simulator::Run ()会运行到设定的stop时间后退出,由Simulator::Destroy ()释放相关资源。到此仿真结束。

因此仿真过程都是在Simulator::Run ()这个类似于死循环的函数下完成的,它负责执行各个事件。而事件的产生都是由最初的应用层初始化时候发起的,它产生一个发包事件,发包事件被Simulator::Run ()中执行,然后逐层协议往下传递数据包(期间可能还会有中间事件),到最底层会计算信道延时、信噪比、误码等参数,然后产生收包事件。由于收包事件是要由别的节点执行,因此会用到上文所描述的context。而发包事件完成前,又会预定下一个发包事件。因此,应用层发包事件会源源不断的依次产生,而每个发包事件结束前又会产生其它节点的收包事件,其它节点收包事件又接着被执行。该过程会运行,直到应用层stop时间结束。应用层设置启动和结束时间的代码的示例如下:

clientApps.Start (Seconds (2.0));
clientApps.Stop (Seconds (19.0));

 

你可能感兴趣的:(NS3)