目录
基于名字空间的存取 Namespace-based access
通过构造函数和 helper classes 来设置 Setting through constructors
将现存的内部变量加入元数据系统 Adding an existing internal variable to the metadata system
添加新的 TypeId Adding a new TypeId
基于 GTK 的 ConfigStore GTK-based ConfigStore
原文地址:NS3 Attribute和Config内容翻译 - 简书
在NS3的模拟中,主要有两个方面的设置:
- 模拟拓扑和对象是如何连接的
- 在拓扑中实例化的模型所使用的值
对象概述
许多ns-3的对象继承基类ns3::Object。这些对象有很多附加的属性,这些属性是我们为了对系统进行组织和以及改进对象的内存管理而开发的
使用属性系统的ns-3对象是从ns3::Object或ns3::ObjectBase派生的。我们将要讨论的大多数ns-3对象派生自ns3::Object,但一些在smart pointer内存管理框架之外的对象派生自ns3::ObjectBase
Smart pointers
对于大多数基本的用法(语法),将smart pointer看待为常规指针:
Ptr<WifiNetDevice> nd = ...;
nd->CallSomeFunction ();
// etc.
创建对象
创建这样的对象的典型方法如下:
Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice> ();
可以认为这在功能上等价于:
WifiNetDevice* nd = new WifiNetDevice ();
TypeId
ns-3中由类ns3::Object派生出的类可以包含一个叫做TypeId的元数据类,该类记录关于类的元信息,以便在对象聚合以及构件管理系统中使用:识别该类的一个独一无二的字符串
对象小结
将所有这些概念综合在一起,让我们研究一个特定的例子:class ns3::Node。公共头文件node.h中的一个声明包含了一个静态的GetTypeId函数调用:
class Node : public Object
{
public:
static TypeId GetTypeId (void);
...
//该函数在文件 node.cc 中定义如下:
TypeId
Node::GetTypeId (void)
{
static TypeId tid = TypeId (“ns3::Node”)
.SetParent<Object> ()
;
return tid;
}
当用户要创建节点时,可以这样调用:
Ptr<Node> n = CreateObject<Node> ();
属性概述
属性系统的目标是组织对模拟的内部成员对象的访问
功能概述
class DropTailQueue : public Queue
{
public:
static TypeId GetTypeId (void);
...
private:
std::queue<Ptr<Packet> > m_packets;
uint32_t m_maxPackets;
};
考虑用户可能对m_maxPackets的值想要做的事情:为系统设置一个默认值,以便无论何时一个新的 DropTailQueue被创建时,这个成员变量都被初始化成该默认值
对一个已经被实例化过的队列,设置或获取队列的该值
上述情况通常需要提供Set()和Get()函数,以及某些类型的全局默认值,在ns-3的属性系统中,这些值定义和存取器函数被移入类TypeId,例如:
TypeId DropTailQueue::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::DropTailQueue")
.SetParent<Queue> ()
.AddConstructor<DropTailQueue> ()
.AddAttribute ("MaxPackets",
"The maximum number of packets accepted by this DropTailQueue.",
UintegerValue (100),
MakeUintegerAccessor (&DropTailQueue::m_maxPackets),
MakeUintegerChecker<uint32_t> ())
;
return tid;
}
方法 AddAttribute()对该值进行一系列处理:
- 将变量 m_maxPackets绑定到一个字符串”MaxPackets”。
- 提供默认值(100 packets)
- 提供为该值下定义的帮助信息
- 提供”checker”(本例中未使用),可以用来设置所允许的上下界
关键的一点是,现在该变量的值以及它的默认值在属性名字空间中是可访问的,基于字符串“MaxPackets”和 TypeId 字符串
基本用法
NS3案例samples/main-attribute-value.cc
//
// This is a basic example of how to use the attribute system to// set and get a value in the underlying system; namely, an unsigned
// integer of the maximum number of packets in a queue
//
int main (int argc, char *argv[])
{
// By default, the MaxPackets attribute has a value of 100 packets
// (this default can be observed in the function DropTailQueue::GetTypeId)
//
// Here, we set it to 80 packets. We could use one of two value types:
// a string-based value or a Uinteger value
Config::SetDefault ("ns3::DropTailQueue::MaxPackets", StringValue ("80"));
// The below function call is redundant
Config::SetDefault ("ns3::DropTailQueue::MaxPackets", UintegerValue (80));
// Allow the user to override any of the defaults and the above
// SetDefaults() at run-time, via command-line arguments
CommandLine cmd;
cmd.Parse (argc, argv);
}
需要注意的是对Config::SetDefault的两次调用
这表明了我们如何设置随后被实例化的DropTailQueues的默认值。我们举例说明了两种类型的Value 类:
类StringValue和类UintegerValue,这两个类可以被用来将值赋给叫做“ns3::DropTailQueue::MaxPackets” 的属性
现在我们使用底层API来创建一些对象。由于上述对默认值的操作,新建的队列的m_maxPackets值将被初始化为80而不是100
Ptr<Node> n0 = CreateObject<Node> ();
Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice>();
n0->AddDevice (net0);
Ptr<Queue> q = CreateObject<DropTailQueue> ();
net0->AddQueue(q);
这里我们创建了一个单独的节点(节点0)和一个单独的PointToPointNetDevice(NetDevice 0),并将一个DropTailQueue加入到了该PointToPointNetDevice上
我们可以操纵已经实例化过的DropTailQueue的MaxPackets值了,有多种不同的方法来达到这个目的
基于指针的存取
假定存在一个指向相关网络设备的智能指针(Ptr),例如这里net0的指针
改变该值的一种方法是通过存取指向底层的队列的指针,并修改该队列的属性。首先,我们能够通过 PointToPointNetDevice的属性获得指向队列(基类)的指针,该属性叫做TxQueue
PointerValue tmp;
net0->GetAttribute ("TxQueue", tmp);
Ptr<Object> txQueue = tmp.GetObject ();
使用函数GetObject,我们能够执行到DropTailQueue的安全的downcast,MaxPackets是DropTailQueue的成员。
Ptr<DropTailQueue> dtq = txQueue->GetObject <DropTailQueue> ();
NS_ASSERT (dtq != 0);
现在我们能够获取该队列上属性的值。由于属性系统存储的是值,而不是类型,所以与Java类似地,我们为底层数据类型引入了封装的 “Value” 类。
这里,属性值被赋值给了一个UintegerValue ,对这个值应用Get()方法会得到
(unwrapped)uint32_t;
UintegerValue limit;
dtq->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("1. dtq limit: " << limit.Get () << "packets");
注意上述的downcast不是必须的。尽管该属性是该子类的成员,我们依然能够使用Ptr<Queue>来做同样的事情
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("2. txQueue limit:" << limit.Get () << "packets");
//现在,让我们将他设置为另一个值(60)
txQueue->SetAttribute("MaxPackets", UintegerValue (60));
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("3. txQueue limit changed: " << limit.Get () << "packets");
基于名字空间的存取 Namespace-based access
另一种获取属性的方法是使用 configuration namespace,属性位于这个名字空间的已经路径上,如果用户无法访问底层指针但又想要使用一条语句来配置某个特定的属性时,这个方法很有用
Config::Set ("/NodeList/0/DeviceList/0/TxQueue/MaxPackets",UintegerValue (25));
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("4. txQueue limit changed through namespace:"<<limit.Get () << "packets");
我们还可以使用通配符来设置所有节点和所有网络设备的该值(例子如下)。
Config::Set ("/NodeList/*/DeviceList/*/TxQueue/MaxPackets",UintegerValue (15));
txQueue->GetAttribute ("MaxPackets", limit);
NS_LOG_INFO ("5. txQueue limit changed through wildcarded namespace: "<<limit.Get () << "packets");
通过构造函数和 helper classes 来设置 Setting through constructors
helper classes任意的属性组合都可以由 helper 和底层 APIs 来设置和获得
通过构造函数本身:
Ptr<Object> p = CreateObject<MyNewObject> ("n1", v1, "n2", v2, ...);
通过高层 helper APIs,比如:
mobility.SetPositionAllocator ("GridPositionAllocator",
"MinX", DoubleValue (-100.0),
"MinY", DoubleValue (-100.0),
"DeltaX”, DoubleValue (5.0),"DeltaY", DoubleValue (20.0),
"GridWidth", UintegerValue (20),
"LayoutType", StringValue ("RowFirst"));
值类 Value classes
读者将注意到新的某 Value 类是 AttributeValue 基类的子类。这些类可以被看做中间类,这些中间类可以被用来将 raw types 转换为可以被属性系统使用的值。属性系统的数据库用一种一般类型来存储许多类型的对象,到该一般类型的转换可以使用中间类(IntegerValue, DoubleValue for “floating point”)来完成,也可以通过字符串来完成
从类型到值的直接隐式转换不是很可行,所以用户可以选择使用字符串还是值:
p->Set (“cwnd”, StringValue ("100")); // string-based setter
p->Set (“cwnd”, IntegerValue (100)); // integer-based setter
对于用户想引入属性系统的新的类型,系统提供一些宏来帮助用户为新的类型声明和定义新的AttributeValue 子类
ATTRIBUTE_HELPER_HEADER
ATTRIBUTE_HELPER_CPP
对属性进行扩展 Extending attributes
ns-3 系统在属性系统下边放置了许多内部值,但毫无疑问,用户将对系统不完善的地方进行扩展以及加入用户自己的类
将现存的内部变量加入元数据系统 Adding an existing internal variable to the metadata system
考虑类 TcpSocket 中的这个变量:
uint32_t m_cWnd; // Congestion window
假设使用Tcp的某个人想要使用元数据系统获得或设置该变量的值。如果 ns-3还没有提供这个,用户可以再元数据系统中添加如下声明
(在TcpSocket的TypeId声明中):
.AddParameter ("Congestion window","Tcp congestion window (bytes)",
Uinteger(1),
MakeUintegerAccessor(&TcpSocket::m_cWnd),MakeUintegerChecker<uint16_t> ());
现在,用户可以使用指向该TcpSocket的指针来执行设置和获取操作,而不用显式添加这些函数。此外,访问控制可以被应用,比如使得该参数只读不可写和对参数进行上下界检查
添加新的 TypeId Adding a new TypeId
现在我们讨论用户如何往 ns-3 系统中添加新的类
我们已经介绍过类似如下的 TypeId 定义:
TypeId
RandomWalk2dMobilityModel::GetTypeId (void)
{
static TypeId tid = TypeId (“ns3::RandomWalk2dMobilityModel”)
.SetParent<MobilityModel> ()
.SetGroupName (“Mobility”)
.AddConstructor<RandomWalk2dMobilityModel> ()
.AddAttribute (“Bounds”,
"Bounds of the area to cruise.",
RectangleValue (Rectangle (0.0, 0.0, 100.0, 100.0)),
MakeRectangleAccessor (&RandomWalk2dMobilityModel::m_bounds),
MakeRectangleChecker ())
.AddAttribute (“Time”,
"Change current direction and speed after moving for this delay.",
TimeValue (Seconds (1.0)),
MakeTimeAccessor (&RandomWalk2dMobilityModel::m_modeTime),
MakeTimeChecker ())
// etc (more parameters).
;
return tid;
}
类声明中与此相关的声明是一行公共成员方法:
public:
static TypeId GetTypeId (void);
典型的错误包括:
- 没有调用 SetParent 方法,或者使用了错误的类型来调用他
- 没有调用 AddConstructor 方法,或者使用了错误的类型来调用他
- 在 TypeId 的构造函数中对于 TypeId 的名字引入了印刷错误
- 没有使用封装类的全限定C++类型名作为 TypeId 的名字
- 以上错误都无法被 ns-3 探测到,所以用户应当多次检查以确保正确性
给属性系统中添加新的类
从用户的角度来看,编写新的类并将其加入属性系统主要是编写字符串与属性值之间的转换。多数可以通过“宏化的“(macro-ized)代码来复制/粘贴。例如目录 src/mobility/ 下的类 Rectangle:类声明中加入一行:
/**
* brief a 2d rectangle
*/
class Rectangle
{
...
};
在类声明的下边加入一个宏调用和两个操作符:
std::ostream &operator << (std::ostream &os, const Rectangle &rectangle);
std::istream &operator >> (std::istream &is, Rectangle &rectangle);
ATTRIBUTE_HELPER_HEADER (Rectangle);
类定义的代码类似于:
ATTRIBUTE_HELPER_CPP (Rectangle);
std::ostream &
operator << (std::ostream &os, const Rectangle &rectangle)
{
os << rectangle.xMin << "|" << rectangle.xMax << "|" << rectangle.yMin <<
"|" << rectangle.yMax;
return os;
}
std::istream &
operator >> (std::istream &is, Rectangle &rectangle)
{
char c1, c2, c3;
is >> rectangle.xMin >> c1 >> rectangle.xMax >> c2 >> rectangle.yMin >>
c3 >> rectangle.yMax;
if (c1 != '|' ||c2 != '|' ||c3 != '|')
{
is.setstate (std::ios_base::failbit);
}
return is;
}
这些流操作符将字符串表示形式的 Rectangle(“xMin|xMax|yMin|yMax”)转化为底层的 Rectangle,模块的编写者必须指定新类的这些操作符以及该类的实例的字符串句法表示形式
ConfigStore
请求反馈: 这是 ns-3 的一个试验性的特色。他不在主要的代码树中。如果您喜欢该特色并愿意提供关于他的反馈,请给我们写电子邮件。ns-3 的属性的值可以被存储在 ascii 文本文件中,并在将来的模拟中加载。这个特色被认为是 ns-3 的 ConfigStore
ConfigStore 的代码在src/contrib/下。因为我们还在寻求用户的反馈,所以目前还不在主要的代码树中。我们用一个例子来探索这个系统。将文件 csma-bridge.cc 复制到 scratch目录:
cp examples/csma-bridge.cc scratch/
./waf
我们编辑该文件以加入ConfigStore特色。首先,添加一个include语句,然后加入以下行:
#include “contrib-module.h”
...
int main (...)
{
// setup topology
// Invoke just before entering Simulator::Run ()
ConfigStore config;
config.Configure ();
Simulator::Run ();
}
存在一个控制Configure()的属性,他决定Configure()是将模拟的配置存储在文件中并退出,还是加载模拟的配置文件并继续执行。首先,属性LoadFilename被检查,如果不为空,则程序从所提供的文件名来加载配置;如果为空,且属性StoreFilename 被提供,则配置将被写入指定的输出文件
虽然生成一个配置文件的样本并修改一些值是可能的,但有些情况这种方法是行不通的,因为对于同一个自动生成的配置文件,同一个对象上的同一个值可能在不同的配置路径上出现多次
同样地,使用这个类的最好方法是用他生成一个初始的配置文件,仅从该文件中提取严格必须得元素,并将这些元素移动一个新的配置文件。这个新的配置文件在随后的模拟中可以被安全地编辑和加载
以此为例运行一次程序来创建一个配置文件。如果你使用的是 bash shell,那么下边的命令应该能够工作(阐明了如何从命令行设置属性):
./build/debug/scratch/csma-bridge –ns3::ConfigStore::StoreFilename=test.config
如果上述命令不起作用(上述命令需要 rpath 的支持),试试如下:
./waf –command-template=”%s –
ns3::ConfigStore::StoreFilename=test.config” –run scratch/csma-bridge
//运行该程序将产生一个叫做”test.config”的输出配置文件,类似于如下:
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Addre
ss 00:00:00:00:00:01
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Frame
Size 1518
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/SendE
nable true
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Recei
veEnable true
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/TxQue
ue/$ns3::DropTailQueue/MaxPackets 100
/$ns3::NodeListPriv/NodeList/0/$ns3::Node/DeviceList/0/$ns3::CsmaNet
Device/Mtu 1
500
...
上边列出了拓扑脚本中的每一个对象以及每一个注册过的属性的值。此文件的语法是每行都标明了属性独一无二的名字,名字后边是值
该文件是某个给定模拟中的参数的一个方便的记录,可以使用模拟输出文件来存储。此外,该文件还可以被用来将模拟参数化,而不是编辑脚本或传递命令行参数。比如:检查并调整一个已经存在的配置文件中的值,然后将该文件传递给模拟程序
相关的命令:
./build/debug/scratch/csma-bridge –ns3::ConfigStore::LoadFilename=test.config
//如果上述命令不起作用(上述命令需要 rpath 的支持),试试如下:
./waf –command-template=”%s –ns3::ConfigStore::LoadFilename=test.config” –run scratch/csma-bridge
基于 GTK 的 ConfigStore GTK-based ConfigStore
对于 ConfigStore,存在一个基于 GTK 的前端。这使得用户可以使用 GUI 来存取和修改变量。该特色的屏幕截图可以在 ns-3 Overview 找到。要使用这个特色,必须安装 libgtk 和 libgtk-dev。Ubuntu 下安装命令的示例:
sudo apt-get install libgtk2.0-0 libgtk2.0-dev
通过 ./waf configure 阶段的输出来检验是否已经配置好:
—- Summary of optional NS-3 features:
Threading Primitives : enabled
Real Time Simulator : enabled
GtkConfigStore : not enabled (library ‘gtk+-2.0 >= 2.12′ not found)
在上述例子中,GtkConfigStore 没有开启,要想使用他,必须安装合适的版本,并且再次执行
./waf configure
./waf
用法与non-GTK-based版本几乎一样:
// Invoke just before entering Simulator::Run ()
GtkConfigStore config;
config.Configure ();
现在,当你运行脚本时将弹出一个 GUI,使得你可以打开不同节点/对象上属性的菜单,配置好之后启动模拟