NS3网络仿真项目(三)—— Events & Simulator

NS3是一种离散事件网络仿真器。从概念上讲,该仿真器会持续追踪一系列事件的发生,通过事件的调度使其在特定的仿真时间被执行。仿真器的工作就是按仿真时间顺序执行离散事件。举个具体例子来说,如果一个事件在仿真时间100s时被执行,而下一个事件发生在仿真时间200s时,仿真器的仿真时间会立刻从100s变为200s去执行下一个事件。这就是所谓的“离散事件“仿真器。

本文主要介绍:事件Event的建立和取消,仿真器的调度和仿真时间的提取。

1. Event

EventId类 可以用来作为一个事件的标识,进而可以对指定的事件进行取消和移除等操作,下面结合Simulator进行说明。

2. Simulator

Simulator类是访问事件调度工具的公共入口点。在开始仿真时,会调度一些事件至事件列表中,通过进入仿真器主循环(即Simulator::Run)按仿真时间顺序执行他们。仿真器执行事件期间,调度器(Scheduler)会插入或移除某些事件至事件列表中。直到事件列表中没有事件需要被执行或强制调用了Simultor::Stop函数,此时仿真结束。

关于调度器的使用,这里给出几个例子

例1:客户端Client按照某一指定的时间间隔m_interval不断发送数据包

class UdpClient : pubic Application
{
public:
    ...

private:
    virtual void StartApplication(void);
    virtual void StopApplication(void);
    void Send(void);
    ...
    Time m_interval;
    EventId m_sendEvent;
    ...
}

UdpClient::StartApplication执行客户端的启动过程,在进行一系列启动操作(套接字绑定)后,将“数据发送事件”插入事件列表,从仿真时间来看启动UdpClient后立即进入数据发包流程,此时有

m_sendEvent = Simulator::ScheduleNow(&UdpClient::Send, this);

第一个变量指定需要执行的一个类中的函数;第二个变量指定该类的一个对象,如果是当前对象本身用this。

然后进入周期性发包过程,发包间隔事先需要配置或写到默认参数中。

在UdpClient::Send函数中,插入如下事件,就可以实现周期性发包。

m_sendEvent = Simulator::Schedule(m_interval, &UdpClient::Send, this);

当StopApplication事件被执行时,需要取消周期性发包的自调用过程,因此有

Simulator::Cancel(m_sendEvent);

这里调度的函数是无参函数,当调度的函数时有参函数时,需要连同参数一起写入。例2给出调用有参函数的Simulator::Schedule用法。

例2:数据包进入交换机后,经历一个仿真延迟时间m_delay,再转发至出端口。

class SwitchNetDevice : public NetDevice
{
public:
    ...
//交换机转发数据包函数
    void ForwardUp(Ptr incomingPort, Ptr packet, uint16_t protocol, Mac48Address src, Mac48Address dst);
    ...

protected:
    ...
//交换机从网卡接收到数据包函数
    void ReceiveFromDevice(Ptr device, Ptr packet, uint16_t protocol, Mac48Address src, Mac48Address dst);

private:
    ...
    Time m_delay;//交换机内部仿真延迟时间
    ...
}

 数据包被交换机网卡接收,此时执行SwitchNetDevice::ReceiveFromDevice函数,经过仿真延迟时间m_delay后执行交换机的转发功能。因此在SwitchNetDevice::ReceiveFromDevice函数最后执行以下命令

Simulator::Schedule(m_delay,&NutsSwitchNetDevice::ForwardUp,this,incomingPort, packet, protocol, src48, dst48);

Simulator::Schedule参数中给出SwitchNetDevice::ForwardUp函数所需要的仿真参数。

关于交换机的程序设计思路之后的文章会做进一步介绍。

例3:(Simulator调度的对象不是当前对象时的使用)寻找匹配目的地址的网卡设备,并由匹配成功的网卡执行数据包处理操作。

for(int i = 1 ; iGetNDevices ();++i) // 此处i从1开始与GetNDevices()的返回值有关
	{
		Ptr  device =  m_node->GetDevice(i);
		Ptr  pppdevice = device->GetObject();
		Mac48Address addr48 = Mac48Address::ConvertFrom(device->GetAddress());
		if(addr48 == dst48)
		{
			CNMHeader cnmHeader;
			packet->PeekHeader(cnmHeader);
			uint32_t triggeredPriority = cnmHeader.GetEncapPriority();
			Simulator::ScheduleNow(&PointToPointNetDevice::ProcessCnm,pppdevice,packet,triggeredPriority);
		}	
	 }

此处事件的调用对象是pppdevice,该对象从以下命令中提取

 Ptr  pppdevice = device->GetObject();

Simulator::ScheduleNow中的packet和triggeredPriority为PointToPointNetDevice::ProcessCnm函数的参数。具体的函数功能暂时不用管,掌握Simulator函数对事件的调度和执行方式是本文的重点。

3. Time

NS3的Time类可以用来标识仿真时间,也可以对仿真的时间精度进行修改。默认时间精度是“纳秒ns”。如果程序中需要使用更高的时间精度(如皮秒)时,一定要记得在程序开始时调整时间精度,否则将会出现以下问题:

当执行PicoSecond(1).GetPicoSeconds(),会返回0,因为此时的时间精度不够。

要想使用皮秒级时间精度,可以用以下命令进行调整:

Time::SetResolution (Time::PS);

值得注意的是,提高时间精度意味着减小了仿真的时间范围。因为全局仿真时间变量为64bits整数型数据,数据最大值为2^64。当时间精度为皮秒时,最大仿真时间为2^64ps。

最后,需要提醒一下,不要在创建时间对象之后修改仿真时间精度!如果在调用Time::SetResolution之前创建了时间对象,这些对象都始终为更新精度之前的数值,在数值不变,时间精度发生改变的情况下就会引起错误。

下面给出几个提取仿真时间的例子

例1:提取当前仿真时间(使用默认时间精度ns)

uint64_t now = Simulator::Now().GetNanoSeconds();

例2:仿真时间可以进行算术运算和对比等操作。此处为仿真时间对比示例,定义一个结束时间,判断当前仿真时间是否到达结束时间,如果当前仿真时间小于结束时间则进一步执行相关操作。

Time m_endtime = MicroSeconds(2);
...

 if( m_endtime.Compare(Simulator::Now()) == 1 )
{
...
}

Time::Compare函数功能是判断m_endtime与Simulator::Now()大小,该函数在NS3的nstime.h文件中

  inline int Compare (const Time &o) const
  {
    return (m_data < o.m_data) ? -1 : (m_data == o.m_data) ? 0 : 1;
  }

此时m_data为m_endtime时间,o.m_data为Simulator::Now()时间。

当m_endtime < Simulator::Now()时 返回 -1;

当m_endtime = Simulator::Now()时 返回 0;

当m_endtime > Simulator::Now()时 返回 1;

因此,如果当前仿真时间未达到结束时间时,即m_endtime.Compare(Simulator::Now()) == 1,则继续执行if语句中的操作。

 

参考链接:https://www.nsnam.org/docs/release/3.29/manual/html/events.html

你可能感兴趣的:(ns-3网络仿真器)