ns3使用-3
第3章:对象模型架构
对象模型通过三个基类(Object,ObjectBase,和SimpleRefCount)定义了ns-3中大部分C++行为和关系准则。
ns-3中,网络模拟场景由结点,结点中的协议栈,分组和连接结点的信道等多个网络元素组成,每个网络元素对应一个C++基类。
网络元素 | C++类 | 网络元素 | C++类 |
---|---|---|---|
网络结点 | Node | 网络设备 | NetDevice |
应用程序 | Application | 分组 | Packet |
通信信道 | Channel |
这些基类定义了网络元素的基本行为,模拟中真正运行的是这些基类的子类,如NetDevice的子类PointToPointNetDevice和CSMANetDevice。对象模型可以对这些类进行统筹管理。
对象模型的主要作用:
- 基类决定类的共性特征,如动态内存管理和属性配置等,子类只需要实现自身专属特征即可。
- 多个类的管理,例如一个网络结点需要整合应用程序,通信信道,网络设备和TCP/IP层协议多个C++类才能与其他结点进行通信。
对象模型定义的三个基类Object,ObjectBase,和SimpleRefCount
SimpleRefCount针对单个类的动态内存分配,实际上就是将智能指针在ns-3中实现,定义了一个引用计数器,记录自己内存的指针数量,如果计数器变为0,说明无人使用,该对象的动态内存会被ns-3自动释放。
ObjectBase用于配置单个类的属性变量和trace变量,属性和trace变量分别实现了ns-3脚本的参数配置和数据输出功能。子类只需定义自己的属性和trace变量,其余操作由基类完成。
Object类解决了多个类之间的动态关联问题。类之间的动态关联是通过对象聚合的功能实现的。对象聚合可以让Object对象在运行时关联其他对象,而不是在编译时关联。
非对象模型大部分不参与网络信息处理和分组传递,如Internet模块的Ipv4Address和Ipv6Address,存储Node对象的NodeContainer等。
第4章:Ptr智能指针
ns-3智能指针是C++标准库智能指针在ns-3中的实现。普通指针必须通过new和delete成对搭配使用,但是存在安全隐患。编写ns-3程序时应尽量使用智能指针。
- 内存泄漏:动态内存没有被及时释放。
- 空悬指针:在有其他指针使用的情况下释放动态内存。
智能指针可以自动释放没有指针指向的动态内存,提高了程序的安全性。
1 | //Ptr指针和原始指针使用上的区别 |
Create函数用于分配内存,Ptr实现包括两部分,第一部分负责保存原始指针和模拟原始指针,由Ptr类实现,第二部分负责记录所有指向所分配对象内存的指针数量(引用计数器),这一部分通过SimpleRefCount类实现。SimpleRefCount类通过Ref()和Unref()控制计数器增减。当计数器变为0后,Unref()会调用Delete()销毁该对象。引用计数器的值可以通过GetReferenceCount()函数获取。
使用实例:
1 | //1. 初始化 |
Ptr指针的适用范围:
- 只支持构造函数参数少于7个的类。如果超过7个,需要用户实现新的Create()函数。
- 只能用于SimpleRefCount的子类对象,对于ObjectBase的子类和非对象模型类,只能使用C++标准库的函数或运算符。
第5章 对象模型的基石:元信息
5.1 什么是元信息
对象模型两个重要的类Object(实现多个类之间的动态关联),ObjectBase(针对单个类的属性和trace变量配置)。每一个ObjectBase的子类都有且仅有唯一的一组属于自己的元信息。这些子类的元信息集中存储在一个数据结构中,并以类名称为唯一标识符。
元信息种类 | 说明 |
---|---|
类名称 | 所属C++类的名称,一个类名有且仅有一组元信息 |
类构造函数 | 用于创建对象,对用户屏蔽类构造函数的具体细节 |
父类TypeId | 用于类聚合中的类查找,即Object::GetObject()函数 |
属性信息 | 该类的所有属性变量的辅助信息 |
trace信息 | 该类的所有trace变量的辅助信息 |
元信息是实现对象聚合,属性和trace变量配置的基础。对象聚合数组利用元信息中的类名称和父类TypeID查找对象。元信息中的默认属性值可以用于创建对象时的变量初始化。
5.2 元信息存储:lidManager类
在ns-3中,ObjectBase类及其子类的元信息都存储在向量容器(std::vector)中,并以类名称作为查找关键字。一个类的元信息有且仅有一组。
5.3 元信息管理接口:TypeId类
实际上,索引值不是以成员变量的形式存在ObjectBase子类对象的,而是保存在TypeId对象里。TypeId是一个C++类,可以理解为元信息的管理接口。
5.4 TypeId的使用
1.初始化:每一个ObjectBase的子类都有一个GetTypeId()静态函数,其作用是为子类创建TypeId对象,添加元信息。
1 | //src/application/model/udp-echo-client.h |
TypeId()函数在LidManager对象的向量容器中创建一个新的元信息条目,并将其在容器中的索引值存储在TypeId对象tid变量中。这个TypeId对象是一个静态变量。初始化发生在程序编译阶段。模拟过程中,对于所有的UdpEchoClient类的所有对象来说,GetTypeId()函数总是返回编译时就创建好的TypeId变量tid。
2.TypeId运算符
赋值运算符(=),比较运算符(==,<,),流插入(<<)
3.获取TypeId
1 | //ObjectBase::GetTypeId(),获取TypeId |
第6章:Object类
Object类继承了ObjectBase和SimpleRefCount类,实现了对象聚合功能。
传输层和网络层对象统一以Object基类指针的形式存储在Node对象的一个指针数组里,这个数据就是对象聚合使用到的指针数组,也叫聚合数组。Node中安装的大部分协议对象都存在这个指针数组里,但也有例外,Application和NetDevice就单独存在两个指针向量容器中。
6.1 对象聚合的技术原理
简化了关联对象的获取方法,可以使用Object::GetObject()函数来获取该协议对象的指针。
1 | //使用Object::GetObject()函数获取协议对象的指针 |
通过定义了一个指针数组,将关联对象的存储,获取和设置都统一了起来。关联对象具有统一的存储格式,统一的提取方法和统一的设置方法,将这些操作都放在Object基类中统一解决。
1 | class object:public SimpleRefCount<Object, ObjectBase, ObjectDeleter> { |
聚合数组所有元素的类名称可以通过遍历Object::AggregateIterator变量得到。
1 | Object::AggregateIterator iter = node.Get(0)->GetAggregateIterator(); |
一个类在对象聚合数组中最多只能有一个实例对象。例如,一个Node对象中只需要一个TCP对象和一个IPV4对象。而想Application和NetDevice需要一个Node对象有多个对象的协议就需要向量容器来存储。对象聚合以牺牲效率换取对象关联的灵活性。
多个对象之间关联使用对象聚合比较合适。两个固定对象的关联还是使用回调函数或者私有成员变量更合理。
6.2 Object的创建与获取
1.Object的创建主要有两种方法:包括CreateObject()函数和ObjectFactory()类,分别用于创建一个或多个Object类或其子类对象。
1 | //CreateObject()函数返回值是所创对象的Ptr指针,可以调用不同形参 |
2.获取Object,即在一个对象的聚合数组中获取指定对象的指针,使用的函数是Object::GetObject();这个函数是一个模板函数,用于对象查找。
1 | Ptr<Ipv4> ipv4 = node->GetObject<Ipv4>(); |
GetObject()函数会首先获取模板中类名称的TypeId变量,然后将这个变量依次与聚合数组每个元素的TypeId比较,直到匹配到元素或者遍历完整个数组为止。
第7章 ObjectBase类
ns-3脚本的参数配置和数据输出功能都由ObjectBase基类定义。
7.1 创建属性
属性的本质是一个C++类的成员变量。而属性系统是连接属性和成员变量的桥梁,其作用为将类内部的成员变量变为外部可配置的属性参数。属性的创建流程如下:
1 | class PointToPointNetDevice{ |
PointToPointNetDevice的传输速率是一个私有成员变量m_bps;PointToPointNetDevice::GetTypeId内部调用AddAttribute()函数添加属性辅助信息。有了属性辅助信息后,一个属性变量就算是创建完成了,属性辅助信息最关键的域有3个。
- DataRate:属性名,名字是类在属性中的唯一标识。
- DataRate(“32786b/s”):默认状态下对象创建的属性值。
- MakeDataRateAccessor:创建了属性访问函数,关联属性辅助信息和C++中的私有成员变量
属性辅助信息存储的不是属性值本身,而是属性名,一个类的属性在不同对象中共享同一组属性辅助信息。就类似元信息针对的是类而非类的对象。
助手类,命令行和Config::SetDefault()三种方法实际上修改的是属性辅助信息的属性默认值。
Config::Set()函数则不同,其修改的是对象中的成员变量值而非属性辅助信息。其实际上首先通过配置路径中的类名称ns3::PointToPointNetDevice在LidManager的向量容器中查找到相应的元信息,然后用属性名DataRate找到属性辅助信息列表中对应的条目。最后调用其中存储的访问函数指针把m_bps成员变量值设置为5Mbps。
1 | //设置结点0中PPP网络设备的发送速率为5Mbps |
7.1.2 属性类型
主要属性类型包括:
ns-3不支持不同类型属性之间的直接转换。但所有属性都可以转换为字符串属性StringValue。这就是为什么first脚本中看到的传输速率和传播延时两个属性使用的都是StringValue类型,实际上应该是DataRateValue和TimeValue。
有些时候用StringValue类型代替原格式更简单,比如DataRateValue,TimeValue和EnumValue。
7.1.3 ConfigStore
ConfigStore是属性配置中很有用的一个C++类,它可以保存和读取脚本中使用过的属性值。适合自动化的多批次模拟。
ConfigStore是ObjectBase的子类,具有三个属性:Mode,Filename,FileFormat。
下面这段代码将脚本的属性值以纯文本形式保存在first-output-attribute.txt的文件中,这段代码一般在整个脚本后,Simulator::Run()之前
1 | //配置属性保存方式 |
使用这些函数需要在脚本中包括config-store-module.h
头文件
属性加载:将Mode属性改为”Load”;
7.1.4 全局属性
全局属性是一种特殊的属性,其不属于任何一个类,因此全局属性的辅助信息不在IidManager元信息结构中存储。而是存在GlobalValue类的一个静态向量结构中。
全局变量都是一些影响模拟器行为或性能的变量,可以通过waf的”—PrintGlobals”得到
1 | ./waf --run "first --PrintGlobals" |
7.2 trace变量
trace变量是C++类中的一个函数指针成员变量。trace系统的作用就是把这个函数指针变成一个可以在脚本中配置的trace变量。每当有特定的网络行为发生时,相应的函数指针就会被调用。
下面是trace变量创建的一个例子
1 | class PointToPointNetDevice:public NetDevice { |
PointToPointNetDevice类的m_macRxTrace变量是一个TracedCallback对象。暂时类比为一个函数指针。(函数返回值为void,形参为Ptr<const Packet>
)。当把这个函数指针的相关辅助信息添加到元信息系统中后,就形成了一个可以在脚本中配置的trace变量,与属性辅助信息一样。其中最关键的信息也有三个,如上面的代码所示。
7.2.2 trace类型
严格来说,所有的trace变量都属于函数指针类型,区别就在于函数指针签名格式的不同。签名格式主要有两种:TracedCallBack签名格式和TracedValue签名格式。前者主要用于分组相关的行为事件(如分组收发,丢失等),后者主要用于一个数值变量的大小变化事件(如TCP拥塞窗口的大小变化,一个队列中分组数量变化等)。
1.TracedCallback签名类型
这种签名格式的返回值均为void,形参最多有8个,签名格式一般为ns3::<类名称或名字空间>::<typedef名>
,其中typedef名的后缀一般都是TracedCallBack,因为这些trace变量对应的函数指针都是TracedCallBack类的对象。
2.TracedValue签名类型
TracedValue签名类型是TracedCallback签名类型的一个子集,主要用于记录那些数值trace变量的变化。一个典型例子就是TCP的各种窗口大小
1 | class TcpSocketBase:public TcpSocket { |