一、队列
1.1队列基础介绍
以下参考官方文档和ns3学习记录(六):ns3队列
ns-3 中队列规则的选择会对性能产生很大影响,因此用户有必要了解默认安装了什么,以及如何更改默认值并观察其性能。
从架构上讲,ns-3 将设备层与互联网主机的 IP 层或流量控制层分开。自 ns-3 最新版本发布以来,传出数据包在到达通道对象之前要经过两个队列层。遇到的第一个队列层在 ns-3 中被称为 "流量控制层";在这里,主动队列管理(RFC7567)和服务质量(QoS)优先级是通过使用队列规则以独立于设备的方式进行的。第二个队列层通常存在于 NetDevice 对象中。不同的设备(如 LTE、Wi-Fi)对这些队列有不同的实现方式。这种双层方法反映了实际情况(提供优先级的软件队列和特定于链路类型的硬件队列)。实际上,情况可能比这还要复杂。例如,ARP协议有一个小队列。Linux 中的 Wi-Fi 有四层队列 (https://lwn.net/Articles/705884/)。
只有当设备队列满时,NetDevice 通知流量控制层,流量控制层才能停止向 NetDevice 发送数据包。否则,队列纪律的backlog(累计未完成的待处理事件,即接收队列的大小)总是空的,它们就不起作用。目前,以下使用队列对象(或队列子类对象)存储数据包的 NetDevice 支持流量控制,即通知流量控制层的功能:
- Point-To-Point
- Csma
- Wi-Fi
- SimpleNetDevice
队列规则的性能受 NetDevice 所用队列大小的影响很大。目前,ns-3 中的队列默认情况下不会根据配置的链路属性(带宽、延迟)进行自动调整,而且通常是最简单的变体(例如,具有Drop Tail行为的 FIFO 调度,Drop Tail:当路由器队列长度达到最大值时,通过丢包来指示拥塞,先到达路由器的分组首先被传输。一旦发生丢包,发送端立即被告知网络拥塞,从而调整发送速率)。不过,队列的大小可以通过启用 BQL(字节队列限制)来动态调整,BQL 是 Linux 内核中实现的一种算法,用于调整设备队列的大小,以对抗缓冲区膨胀,同时避免饥饿。目前,支持流量控制的 NetDevice 都支持 BQL。
1.2 ns-3 中可用的队列模型
- PFifoFastQueueDisc: The default maximum size is 1000 packets
- FifoQueueDisc: The default maximum size is 1000 packets
- RedQueueDisc: The default maximum size is 25 packets
- CoDelQueueDisc: The default maximum size is 1500 kilobytes
- FqCoDelQueueDisc: The default maximum size is 10240 packets
- PieQueueDisc: The default maximum size is 25 packets
- MqQueueDisc: This queue disc has no limits on its capacity
- TbfQueueDisc: The default maximum size is 1000 packets
默认情况下,当 IPv4 或 IPv6 地址分配给与 NetDevice 相关联的接口时,会在 NetDevice 上安装 pfifo_fast 队列规范,除非 NetDevice 上已经安装了队列规范。
在设备层,有设备特定队列:
- PointToPointNetDevice: The default configuration (as set by the helper) is to install a DropTail queue of default size (100 packets) 默认配置时安装一个大小为100个包的去尾队列
- CsmaNetDevice: The default configuration (as set by the helper) is to install a DropTail queue of default size (100 packets) 默认配置时安装一个大小为100个包的去尾队列
- WiFiNetDevice: The default configuration is to install a DropTail queue of default size (100 packets) for non-QoS stations and four DropTail queues of default size (100 packets) for QoS stations 默认配置时没有QoS的站点安装一个大小为100个包的去尾队列,有QoS的站点安装四个大小为100个包的去尾队列
- SimpleNetDevice: The default configuration is to install a DropTail queue of default size (100 packets) 默认配置时安装一个大小为100个包的去尾队列
- LteNetDevice: Queueing occurs at the RLC layer (RLC UM default buffer is 10 * 1024 bytes, RLC AM does not have a buffer limit). 排队发生在RLC层(RLC UM的默认缓冲区是10 × 1024字节,RLC AM没有缓冲区限制)
- UanNetDevice: There is a default 10 packet queue at the MAC layer 在MAC层上有一个默认的10个数据包队列
1.3 修改队列的默认值
1.NetDevice 使用的队列类型通常可以通过设备辅助程序修改:(设置网络模式之后)
NodeContainer nodes;
nodes.Create(2);
PointToPointHelper p2p;
p2p.SetQueue("ns3::DropTailQueue", "MaxSize", StringValue("50p")); //这里改
NetDeviceContainer devices = p2p.Install(nodes);
2.可以通过流量控制助手修改 NetDevice 上安装的队列盘类型: (安装协议栈之后)
InternetStackHelper stack;
stack.Install(nodes);
TrafficControlHelper tch;
tch.SetRootQueueDisc("ns3::CoDelQueueDisc", "MaxSize", StringValue("1000p")); //这里改
tch.Install(devices);
3.可通过流量控制助手在支持 BQL 的设备上启用 BQL:
InternetStackHelper stack;
stack.Install(nodes);
TrafficControlHelper tch;
tch.SetRootQueueDisc("ns3::CoDelQueueDisc", "MaxSize", StringValue("1000p"));
tch.SetQueueLimits("ns3::DynamicQueueLimits", "HoldTime", StringValue("4ms")); //BQL多这一行
tch.Install(devices);
二、Tracing系统
以下参考ns-3中的数据跟踪与采集——Tracing系统综述及fourth脚本
从 ns-3 中获取输出结果有两种基本策略:使用通用的预定义批量输出机制(logging系统);或将采集到的数据直接存放在一个文件中以便后期处理与分析的Tracing系统。
ns-3 追踪系统分为三个部分:Tracing Sources,Tracing Sinks,以及将Tracing Sources和Tracing Sinks关联起来的方法。
Tracing Sources是一个实体,它可以用来标记仿真中发生的时间,也可以提供一个访问底层数据的方法。例如,Tracing Sources可指示网络设备何时接收到数据包,并为感兴趣的Tracing Sinks提供对数据包内容的访问。当模型中发生状态变化时,跟踪源也会发出指示。例如,TCP 模型的拥塞窗口发生变化时,连接的Tracing Sinks都会收到新旧值的通知。
Tracing Sources提供信息,而Tracing Sinks消费信息。Tracing Sources本身不起任何作用,只有当它和一段有实际功能的代码相关联时才有意义,这段代码就是使用Tracing Sources提供的信息来做相关事物的。使用(或消费)Tracing Sources提供信息的实体称为Tracing Sink。
一个Tracing Sources产生的信息可以没有Tracing Sink消费,也可以有一个或者多个Tracing Sink消费。它们之间是一对多的关系。相同的信息根据不同的代码可以做完全不同的事情。
三、fourth.cc介绍
fourth.cc是一个最简单的跟踪示例,打印要跟踪数据的值的变化。
3.1 类的引入
#include "ns3/object.h"
#include "ns3/simulator.h"
#include "ns3/trace-source-accessor.h"
#include "ns3/traced-value.h"
#include "ns3/uinteger.h"
#include <iostream>
using namespace ns3;
首先要定义自己的类,该类的父类为Object,因此要引入头文件 #include “ns3/object.h”。
再引入ns-3自定义的无符号整形所声明的头文件 #include “ns3/uinteger.h”。
#include "ns3/traced-value.h"头文件中引入了要跟踪数据的类型,即TracedValue。
#include "ns3/trace-source-accessor.h"头文件中包含了本程序要使用的能把自定义数据转换为Trace Sources的函数。
3.2 构建一个自己的类(MyObject)
class MyObject : public Object //定义类名并声明父类
{
public:
/**
* Register this type.
* \return The TypeId.
*/
static TypeId GetTypeId() //定义函数名称
{
static TypeId tid = TypeId("MyObject")
.SetParent<Object>()
.SetGroupName("Tutorial")
.AddConstructor<MyObject>()
.AddTraceSource("MyInteger",
"An integer value to trace.",
MakeTraceSourceAccessor(&MyObject::m_myInt),
"ns3::TracedValueCallback::Int32");
return tid;
}
MyObject()
{
}
TracedValue<int32_t> m_myInt; //!< The traced value.
};
因为Tracing系统和属性系统有很大关联,而属性系统和对象相关联,所以,每一个要追踪的数据都必须属于一个特定的类,这里这个类定义为MyObject,要追踪的数据为m_myInt。
GetTypeId()函数定义了一个关于MyObject的元数据集,并返回标识类TypeId。
ns-3中所有由Object类派生的类都可以包含在一个叫TypeId的元数据类,该类用来记录关于类的元信息,以便在对象聚合以及构建管理中使用,TypeId 类中设计了用唯一的字符串来标识一个类、子类的基类以及子类中可以访问的构造函数。
SetParent()函数的作用是表明该类和父类的关系,避免在向上或向下类型转换时发生不必要的错误。
AddConstructor()函数的作用是在用户使用对象工厂机制时,不用考虑类的具体细节就可以创建对象。
AddTraceSource()函数的作用是提供一个“挂钩”,把自定义的名为“MyInterger”的Trace Source与系统外相关联。第二个参数是附加输出的字符串。第三个参数的参数:&MyObject::m_myInt,这是添加到类中的 TracedValue。最后一个参数是 TracedValue 类型的类型定义名称,以字符串形式表示。它用于为正确的回调函数签名生成文档,这对更多的回调类型非常有用。
MakeTraceSourceAccessor()函数的作用是使MyObject类的成员变量m_myInt成为一个可存取的Trace Sources。
TracedValue<> 声明提供了驱动回调过程的基础结构。当底层值发生变化时,TracedValue 机制将提供该变量的新旧值,在本例中是一个 int32_t 值。
3.3 定义回调函数Trace Sink
void
IntTrace (int32_t oldValue, int32_t newValue)
{
std::cout << "Traced " << oldValue << " to " << newValue << std::endl;
}
上述代码就是定义的回调函数,就是Tracing系统中的Trace Sink。
函数的行为就是输出Trace Source改变前后的值。
3.4 主函数
int
main (int argc, char *argv[])
{
Ptr<MyObject> myObject = CreateObject<MyObject> ();
myObject->TraceConnectWithoutContext ("MyInteger", MakeCallback (&IntTrace));
myObject->m_myInt = 1234;
}
主函数中定义了一个类对象实例myObject,这个实例中包含了一个TraceSource(也就是MyInteger)。
下面一个函数TraceConnectWithoutContext将Trace Sources和Trace Sink相关联。只要调用了这个函数,当Trace Sources数据m_myInt发生改变时,IntTrace函数才会被调用。
最后一行代码可以看作是把常量1234赋值给m_myInt,这时系统会识别这一行为,并将m_myInt赋值前和赋值后的两个值作为形参传递给Trace Sink的回调函数IntTrace。
运行结果
old值为0,新值为1234。