ns-3是基于离散事件调度的网络仿真开源项目,在介绍其default与realtime两种仿真模式前,先给各位看官介绍一下什么是离散事件仿真。
离散事件仿真(Discrete Event Simulation,缩写为DES),它关注的是这样的一类系统:系统的状态只在某一时点发生变化,而状态的变化是由于事件发生造成的。
以银行系统为例(银行系统是DES的经典案例,如同cat之于模式识别),上图展现了一个简单的银行系统。在5分钟时首个顾客到达,由于没有其他顾客,他可以立即接受服务,此时假设他要接受服务的时间为11分钟,那么在16分钟时,他将离开银行。第二个顾客到达的时间是10分钟,由于前面有一个顾客占用了服务台(假设此银行是单服务台),所以他需要排队6分钟,假设他需要接受8分钟的服务,那么顾客二的离开时间就是24分钟。
可以看出,我们把顾客到达,顾客排队,顾客接受服务,顾客离开抽象为事件event,而系统的状态只在一些离散的时间点发生变化(5min,待接受服务顾客为1,系统空闲;10分钟待接收顾客为1,系统繁忙......)。因此在对此类系统进行仿真时,我们可以进行时间跃进,即time hoops,将系统的状态的变化定义在事件的发生时刻,至于在5min-10min,10min-16min等这些时间段的模拟,我们并不关心。
顾客1达到时间是5min,这是仿真的起始参数,至于为什么顾客2的到达时间是10min,为什么顾客1要接受11分钟的服务,顾客2要接受8分钟的服务,是基于一种统计规律的,例如泊松到达过程等等。
在ns-3中事件被定义为函数,事件的发生就是函数的调用,可以用函数去模拟不同的过程,如发送信号、接受信号、发包等等。查看源码core/make-event.h,我们可知,ns-3实现了7种重载来将一个函数生成为一个事件。即这个函数的入参可以是0个—6个,但是所有函数都必须是无返回参数的,例如6个入参的函数。
template <typename U1, typename U2, typename U3, typename U4, typename U5, typename U6,
typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
EventImpl * MakeEvent (void (*f)(U1,U2,U3,U4,U5,U6), T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6)
当然在C++的语言下,这个函数也可以是类的成员函数:
template <typename MEM, typename OBJ,
typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
EventImpl * MakeEvent (MEM mem_ptr, OBJ obj,
T1 a1, T2 a2, T3 a3, T4 a4, T5 a5, T6 a6)
其中MEM是类的成员函数入口,OBJ是一个实例的引用。最后留给用户的接口上是:
//两个入参成员函数,发生时间为10秒
Simulator::Schedule (Seconds (10.0),&MyClass::MyClassFun, &MyInstance, para1, para1);
//两个入参的普通函数,发生时间为15秒
Simulator::Schedule (Seconds (15.0), &MyNormalFunction,para1,para1);
在仿真开始前我们可以规定事件的发生时刻,这里我们定义为5秒和10秒,其实可以还可以调用ns-3的随机数发生器,随机生成一个符合预期分布的值作为发生时间。生成的事件被装入事件表中等待被调用。默认仿真方式下,仿真流程如下:
默认方式下,仿真时间只是起到一个时序作用,和现实中的时间没有任何关系,例如文章开头的例子中,首个顾客达到的时间是5min,并不是真的要等到现实时间流逝5分钟后,才会发生首个事件,而是直接跳跃,并且系统中记录此时的时间(仿真时间,一个虚拟的时钟)。那么有没有一种可以与实时时间同步的仿真方式呢,答案是肯定的。ns-3中一共实现了四种仿真模式:default, realtime, distributed, null-message,他们是class SimulatorImpl的四个子类,仿真的具体实现也是在这四个子类中完成。后两种适用于分布式仿真,较为复杂,这里暂不作介绍,下节将介绍real-time模式下仿真时间与实时时间的同步方式。