ns3使用入门_基于ns3.44_Part2_配置模块参数的Configuration 和Attributes

 前言

事实上ns3的官方手册很全,相关书籍也是有的,官网先贴在这里:

ns-3 | a discrete-event network simulator for internet systemsa discrete-event network simulator for internet systemshttps://www.nsnam.org/相关的脚本介绍也都有一些:

ns-3.35_wifi-he-network.cc_ns-3网络仿真工具wifi脚本解析_wifi脚本网络拓扑_ns-3wifi6吞吐脚本关键注释_吞吐部分_基础ns-3_ns3.35-优快云博客

ns-3-model-library wifi 浅析_ns-3wifi部分解析_ns-3网络模拟器wifi部分文档分析_Part1_ns3 wifiphy物理层冲突-优快云博客

ns-3-model-library wifi 浅析_ns-3wifi部分解析_ns-3网络模拟器wifi部分文档分析_Part2_yansphy-优快云博客

不过现有的要么版本老旧,要么过于现象化,不够本质,正好我最近分析了下官方手册,在这里分享给大家——需要更加完整的内容,可以直接去官网。

Configuration and Attributes

在 ns-3 模拟中,配置有两个主要方面:

  • 模拟拓扑和对象的连接方式。
  • 在拓扑中实例化的模型使用的值。

本章重点介绍上述第二项:ns-3 用户如何组织、记录和修改 ns-3 中使用的许多值。ns-3 属性系统如何在模拟器中收集跟踪和统计信息的基础。在本章的课程中,我们将讨论设置或修改 ns-3 模型对象使用的值的各种方法。按特异性递增的顺序,这些是:

“特异性”是指表中后面几行中的方法会覆盖由早期方法设置的值,并且通常比早期方法影响更少的实例。在深入研究属性值系统的细节之前,回顾一下 Object 类的一些基本属性会有所帮助。

Object Overview

ns-3 从根本上说是一个基于 C++ 对象的系统。我们的意思是新的 C++ 类(类型)可以像往常一样声明、定义和子类化。许多 ns-3 对象继承自 Object 基类。这些对象具有一些额外的属性,我们利用这些属性来组织系统和改进对象的内存管理:

“元数据”系统,将类名链接到有关对象的大量元信息,包括:

  • 子类的基类
  • 子类
  • 子类的 “attributes” 集
  • 每个属性是可修改还是只读,
  • 每个属性允许的值范围。

引用计数智能指针实现,用于内存管理。 

使用属性系统的 ns-3 对象派生自 Object 或 ObjectBase。我们将讨论的大多数 ns-3 对象都派生自 Object,但少数位于智能指针内存管理框架之外的对象派生自 ObjectBase。让我们回顾一下这些对象的几个属性。

智能指针

如 ns-3 教程中所述,ns-3 对​ 象由引用计数智能指针实现类 Ptr ​管理内存。智能指针在 ns-3 API 中广泛使用,以避免传递对可能导致内存泄漏的堆分配对象的引用。对于大多数基本用法 (语法),请将智能指针视为常规指针:

Ptr<WifiNetDevice> nd = ...;
nd->CallSomeFunction();
// etc.

那么,如何获得指向对象的智能指针,如本示例的第一行所示?

创建对象

正如我们在上​ 面的 内存管理和类 Ptr ​中所讨论的,在最低级别的 API 中,Object 类型的对象不会像往常一样使用 operator new 进行实例化,而是通过名为 CreateObject() 的模板化函数进行实例化。创建此类对象的典型方法如下:

Ptr<WifiNetDevice> nd = CreateObject<WifiNetDevice>();

您可以将其视为在功能上等效于:

WifiNetDevice* nd = new WifiNetDevice();

从 Object 派生的对象必须使用 CreateObject() 在堆上分配。可以从 ObjectBase 派生的那些,例如 ns-3 帮助程序函数以及数据包标头和尾部,可以在堆栈上分配。在某些脚本中,您可能不会在代码中看到很多 CreateObject() 调用;这是因为实际上有一些helper 程序对象正在为您执行 CreateObject() 调用。

TypeId

Ns3做的object基类里面带好了TypeID,所以后面Node之类的只要继承Object,就可有TypeID

从类 Object 派生的 ns-3 类可以包含一个名为 TypeId 的元数据类,该类记录有关该类的元信息,以便在 Object 聚合和组件管理器系统中使用(都在GetTypeId里面了 ):

  • 标识类的唯一字符串。
  • 元数据系统中子类的基类。
  • 子类中的可访问构造函数集。
  • 类的可公开访问的属性(“attributes”)的列表。

对象摘要

将所有这些概念放在一起,让我们看一个具体的例子:类 Node。公共头文件 node.h 有一个声明,其中包含静态 GetTypeId() 函数调用:

class Node : public Object
{
public:
  static TypeId GetTypeId();
  ...
//这在 node.cc 文件中定义如下:

TypeId
Node::GetTypeId()
{
  static TypeId tid = TypeId("ns3::Node")
    .SetParent<Object>()
    .SetGroupName("Network")
    .AddConstructor<Node>()
    .AddAttribute("DeviceList",
                  "The list of devices associated to this Node.",
                  ObjectVectorValue(),
                  MakeObjectVectorAccessor(&Node::m_devices),
                  MakeObjectVectorChecker<NetDevice>())
    .AddAttribute("ApplicationList",
                  "The list of applications associated to this Node.",
                  ObjectVectorValue(),
                  MakeObjectVectorAccessor(&Node::m_applications),
                  MakeObjectVectorChecker<Application>())
    .AddAttribute("Id",
                  "The id(unique integer) of this Node.",
                  TypeId::ATTR_GET, // allow only getting it.
                  UintegerValue(0),
                  MakeUintegerAccessor(&Node::m_id),
                  MakeUintegerChecker<uint32_t>())
    ;
  return tid;

将 ns-3Object 类的 TypeId 视为运行时类型信息 (RTTI) 的扩展形式。C++ 语言包括一种简单的 RTTI,以支持 dynamic_cast 和 typeid 运算符。上述定义中的 SetParent<Object>() 调用与我们的对象聚合机制结合使用,以允许在 GetObject() 期间在继承树中安全地进行向上和向下转换。它还使子类能够继承其父类的 Attributes。AddConstructor<Node>() 调用与我们的抽象对象工厂机制结合使用,以允许我们构造 C++ 对象,而无需强制用户知道她正在构造的对象的具体类。对 AddAttribute() 的三次调用将给定字符串与类中的强类型值相关联。请注意,您必须提供帮助字符串,该字符串可能会显示,例如,通过命令行处理器显示。每个 Attribute 都与用于访问对象中的基础成员变量的机制相关联(例如,MakeUintegerAccessor() 告诉泛型 Attribute 代码如何访问上面的节点 ID)。还有一些 “Checker” 方法用于根据范围限制(例如允许的最大值和最小值)验证值。

当用户想要创建节点时,他们通常会调用某种形式的 CreateObject():

Ptr<Node> n = CreateObject<Node>();

或者更抽象地说,使用对象工厂,你甚至可以在不知道具体的 C++ 类型的情况下创建一个 Node 对象:

ObjectFactory factory;
const std::string typeId = "ns3::Node'';
factory.SetTypeId(typeId);
Ptr<Object> node = factory.Create <Object>();

这两种方法都会导致完全初始化的属性在生成的 Object 实例中可用。接下来,我们将讨论如何将属性(与类的成员变量或函数关联的值)插入到上述 TypeId 中。

Attributes

属性系统的目标是组织对模拟的内部成员对象的访问。之所以能实现这个目标,是因为通常在仿真中,用户会剪切和粘贴/修改现有的仿真脚本,或者使用更高级别的仿真结构,但通常会对研究或跟踪特定的内部变量感兴趣。例如,以下用例:

  • 我只想在第一个接入点上跟踪无线接口上的数据包。
  • 我想跟踪特定 TCP 套接字上 TCP 拥塞窗口的值(每次更改时)。
  • 我想要一个 dump 的模拟中使用的所有值。

同样,用户可能希望对模拟中的内部变量进行细粒度访问,或者可能希望广泛更改用于所有后续创建对象中特定参数的初始值。最后,用户可能希望知道哪些变量在仿真配置中是可设置和可检索的。这不仅仅是为了在命令行上直接进行模拟交互;此外,请考虑一个(将来的)图形用户界面,该界面希望能够提供一种功能,使用户可以右键单击画布上的节点,并查看可在节点及其组成成员对象上设置的有组织的参数的分层列表,以及每个参数的帮助文本和默认值。

可用的 AttributeValue 类型

  • AddressValue 地址值
  • AttributeContainerValue 属性容器值
  • BooleanValue 布尔值
  • BoxValue BoxValue (盒子值)
  • CallbackValue 回调值
  • DataRateValue DataRateValue 数据速率值
  • DoubleValue DoubleValue 双值
  • EmptyAttributeValue EmptyAttributeValue (空属性值)
  • EnumValue 枚举值
  • IntegerValue 整数值
  • Ipv4AddressValue
  • Ipv4MaskValue
  • Ipv6AddressValue
  • Ipv6PrefixValue ipv6前缀值
  • LengthValue
  • Mac16AddressValue
  • Mac48AddressValue
  • Mac64AddressValue
  • ObjectFactoryValue ObjectFactoryValue 对象工厂值
  • ObjectPtrContainerValue ObjectPtrContainerValue 对象
  • PairValue<A, B> 对值<A, B>
  • PointerValue
  • PriomapValue Priomap值
  • QueueSizeValue QueueSizeValue 队列大小值
  • RectangleValue 矩形值
  • SsidValue
  • TimeValue 时间值
  • TupleValue<Args…> TupleValue<Args...> TupleValue 能够存储不同类型的值,因此它适用于结构化数据。一个突出的例子是 WifiPhy 的 ChannelSettings 属性,它由信道号、信道宽度、PHY 频段和主要的 20 MHz 信道索引组成。在这种情况下,这些值必须相互一致,这使得很难将它们设置为单独的 Attributes。在 TupleValue 中捕获它们可以简化这个问题,请参阅 src/wifi/model/wifi-phy.cc。存储在 TupleValue 对象中的值可以通过 std::tuple 对象设置/获取,也可以序列化为包含逗号分隔的值序列的字符串/从字符串中反序列化(例如,“{36, 20, BAND_5GHZ, 0}”)。TupleValue 属性的用法如 所示 src/core/test/tuple-value-test-suite.cc 。
  • TypeIdValue
  • UanModesListValue
  • UintegerValue Uinteger值
  • Vector2DValue 矢量 2DValue
  • Vector3DValue
  • WaypointValue WaypointValue 航点值
  • WifiModeValue WifiMode值

定义Attributes

我们为用户提供了一种方法来访问系统深处的值,而不必在系统中探测访问器 (指针) 并遍历指针链来访问它们。考虑一个类 QueueBase,该类具有一个成员变量m_maxSize控制队列的深度。如果我们查看 QueueBase 的声明,我们会看到以下内容:

class QueueBase : public Object {
public:
  static TypeId GetTypeId();
  ...

private:
  ...
  QueueSize m_maxSize;                //!< max queue size
  ...
};
//QueueSize 是 ns-3 中的一种特殊类型,它允许用不同的单位表示大小:

enum QueueSizeUnit
{
  PACKETS,     /**< Use number of packets for queue size */
  BYTES,       /**< Use number of bytes for queue size */
};

class QueueSize
{
  ...
private:
  ...
  QueueSizeUnit m_unit; //!< unit
  uint32_t m_value;     //!< queue size [bytes or packets]
};
//最后,DropTailQueue 类继承自此基类,并提供提交到完整队列的数据包将从队列后面丢弃的语义(“drop tail”)。

/**
 * \ingroup queue
 *
 * \brief A FIFO packet queue that drops tail-end packets on overflow
 */
template <typename Item>
class DropTailQueue : public Queue<Item>

让我们考虑一下用户可能希望使用 m_maxSize 的值执行的作:

  • 为系统设置默认值,以便每当创建新的 DropTailQueue 时,此成员都会初始化为该默认值。
  • 设置或获取已实例化队列的值。

上述内容通常需要提供 Set() 和 Get() 函数,以及某种类型的全局默认值。在 ns-3 属性系统中,这些值定义和访问器函数注册被移动到 TypeId 类中;例如

NS_OBJECT_ENSURE_REGISTERED(QueueBase);

TypeId
QueueBase::GetTypeId()
{
  static TypeId tid = TypeId("ns3::DropTailQueue")
    .SetParent<Queue>()
    .SetGroupName("Network")
    ...
    .AddAttribute("MaxSize",
                  "The max queue size",
                  QueueSizeValue(QueueSize("100p")),
                  MakeQueueSizeAccessor(&QueueBase::SetMaxSize,
                                        &QueueBase::GetMaxSize),
                  MakeQueueSizeChecker())
    ...
    ;

  return tid;
}

AddAttribute() 方法对 m_maxSize 值执行许多作:

  • 将(通常是私有的)成员变量 m_maxSize 绑定到公共字符串 “MaxSize”。
  • 提供默认值 (0 个数据包)。
  • 提供一些定义值含义的帮助文本。
  • 提供可用于设置允许值范围的边界的 “Checker” (本例中未使用) 。

关键点是,现在此变量的值及其默认值可以在属性命名空间中访问,该命名空间基于字符串,例如“MaxSize”和 TypeId 名称字符串。在下一节中,我们将提供一个示例脚本,用于显示用户如何作这些值。请注意,属性的初始化依赖于 NS_OBJECT_ENSURE_REGISTERED(QueueBase) 被调用的宏;如果在 New Class 实现中省略此 Import,则 Attributes 将无法正确初始化。虽然我们已经介绍了如何创建属性,但我们仍然没有介绍如何访问和管理这些值。例如,没有存储这些内容的 globals.h 头文件;属性与它们的类一起存储。自然而然地出现的问题是,用户如何轻松地了解其模型的所有属性,以及用户如何访问这些属性,或将其值记录为模拟记录的一部分?API 文档中提供了为类型定义的实际属性的详细文档,以及所有已定义属性的全局列表。在本文档的其余部分,我们将演示获取和设置属性值的各种方法。

设置默认值

Config::SetDefault 和 CommandLine

可以使用这两种方式进行object默认值的配置,改了再创建,就有默认值改变了——不过也不重要,导出内容不对就小范围修改即可。

Config::SetDefault("ns3::QueueBase::MaxSize", StringValue("80p"));
  // The below function call is redundant
  Config::SetDefault("ns3::QueueBase::MaxSize", QueueSizeValue(QueueSize(QueueSizeUnit::PACKETS, 80)));


CommandLine cmd;
// This provides yet another way to set the value from the command line:
cmd.AddValue("maxSize", "ns3::QueueBase::MaxSize");
cmd.Parse(argc, argv);

现在,我们将使用低级 API 创建一些对象。我们新创建的队列不会m_maxSize初始化为 0 个数据包(如 QueueBase::GetTypeId() 函数中所定义),而是初始化为 80 个数据包,因为我们在上面对默认值进行了作

Ptr<Node> n0 = CreateObject<Node>();

Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice>();
n0->AddDevice(net0);

Ptr<Queue<Packet>> q = CreateObject<DropTailQueue<Packet>> ();
net0->AddQueue(q);

最后,Config::Set...() 如果目标 Attribute 在给定的路径中不存在,则函数将引发错误。还有“故障安全”版本,Config::Set...FailSafe()来验证 Property。如果至少可以设置一个实例,则故障安全版本将返回 true。

构造函数、辅助函数和 ObjectFactory

可以从 helper 和 low-level API 设置和获取任意属性组合;从构造函数本身:

Ptr<GridPositionAllocator> p =
  CreateObjectWithAttributes<GridPositionAllocator>
   ("MinX", DoubleValue(-100.0),
    "MinY", DoubleValue(-100.0),
    "DeltaX", DoubleValue(5.0),
    "DeltaY", DoubleValue(20.0),
    "GridWidth", UintegerValue(20),
    "LayoutType", StringValue("RowFirst"));
//或从更高级别的帮助程序 API 获取,例如:

mobility.SetPositionAllocator
   ("ns3::GridPositionAllocator",
    "MinX", DoubleValue(-100.0),
    "MinY", DoubleValue(-100.0),
    "DeltaX", DoubleValue(5.0),
    "DeltaY", DoubleValue(20.0),
    "GridWidth", UintegerValue(20),
    "LayoutType", StringValue("RowFirst"))

我们在这里不进行说明,但您也可以为特定属性配置具有新值的 ObjectFactory。由 ObjectFactory 创建的实例将在构造期间设置这些属性。这与使用类的帮助程序 API 之一非常相似。回顾一下,有几种方法可以为将来要创建的类实例设置属性的值:

  • Config::SetDefault()
  • 命令行::AddValue()
  • CreateObjectWithAttributes<>()
  • Various helper APIs 

但是,如果您已经创建了一个实例,并且想要更改属性的值,该怎么办?在这个例子中,我们如何作已经实例化的 DropTailQueue 的 m_maxSize 值?以下是各种方法。

更改当前值

SmartPointer

使用指针贴根改

假设手头有指向相关网络设备的智能指针 (Ptr);在当前示例中,它是 net0 指针。更改该值的一种方法是访问指向底层队列的指针并修改其属性。首先,我们观察到我们可以通过 PointToPointNetDevice 属性获取指向(基类)Queue 的指针,其中它称为 “TxQueue”:

PointerValue ptr;
net0->GetAttribute("TxQueue", ptr);
Ptr<Queue<Packet>> txQueue = ptr.Get<Queue<Packet>>();

使用 GetObject() 函数,我们可以对 DropTailQueue 执行安全向下转换。NS_ASSERT 将检查指针是否有效。

Ptr<DropTailQueue<Packet>> dtq = txQueue->GetObject<DropTailQueue<Packet>>();
NS_ASSERT (dtq);

接下来,我们可以获取此队列上属性的值。我们为底层数据类型引入了包装器 Value 类,类似于围绕这些类型的 Java 包装器,因为属性系统存储序列化为字符串的值,而不是不同的类型。在这里,此值的属性值方法生成(未包装的)QueueSize。也就是说,分配给 QueueSizeValue,并且 GetAttribute 方法将变量限制写入 Get()。

QueueSizeValue limit;
dtq->GetAttribute("MaxSize", limit);
NS_LOG_INFO("1.  dtq limit: " << limit.Get());

请注意,上述 downcast 并不是真正需要的;我们可以直接从 txQueue 获取 attribute 值:

txQueue->GetAttribute("MaxSize", limit);
NS_LOG_INFO("2.  txQueue limit: " << limit.Get());

现在,让我们将其设置为另一个值(60 个数据包)。我们还使用 StringValue 速记表示法,通过传入字符串来设置大小(字符串必须是以 p 或 b 字符为后缀的正整数)。

txQueue->SetAttribute("MaxSize", StringValue("60p"));
txQueue->GetAttribute("MaxSize", limit);
NS_LOG_INFO("3.  txQueue limit changed: " << limit.Get());
Config Namespace Path

这个非常有用,能很具体的把参数配置到node上面的device上面的mac phy APP上

获取属性的另一种方法是使用 configuration 命名空间。在这里,此属性位于此命名空间中的已知路径上;如果无法访问底层指针并希望使用单个语句配置特定属性,则此方法非常有用。

Config::Set("/NodeList/0/DeviceList/0/TxQueue/MaxSize",
            StringValue("25p"));
txQueue->GetAttribute("MaxSize", limit);
NS_LOG_INFO("4.  txQueue limit changed through namespace: "
            << limit.Get());

配置路径通常具有 ".../<container name>/<index>/.../<attribute>/<attribute>" 通过容器中对象的索引引用特定实例的形式。在这种情况下,第一个容器是所有 Node的列表;第二个容器是所选节点上所有 NetDevice的列表。最后,配置路径通常以一系列成员属性结尾,在本例中为所选 NetDevice 的“TxQueue”的“MaxSize”属性。我们也可以使用通配符为所有节点和所有网络设备设置这个值(在这个简单的例子中,这与前面的 Config::Set() 具有相同的效果):

Config::Set("/NodeList/*/DeviceList/*/TxQueue/MaxSize",
            StringValue("15p"));
txQueue->GetAttribute("MaxSize", limit);
NS_LOG_INFO("5.  txQueue limit changed through wildcarded namespace: "
            << limit.Get());
//如果您从命令行运行此程序,您应该会看到以下输出,这些输出与我们上面执行的步骤相对应:
$ ./ns3 run main-attribute-value
1.  dtq limit: 80p
2.  txQueue limit: 80p
3.  txQueue limit changed: 60p
4.  txQueue limit changed through namespace: 25p
5.  txQueue limit changed through wildcarded namespace: 15p
Object Name Service

不知道nodelist代表每一个node,就新建一个name绑定到node上

获取属性的另一种方法是使用对象名称服务工具。对象名称服务允许我们使用用户定义的名称字符串将项目添加到“/Names/”路径下的配置命名空间中。如果无法访问底层指针,并且难以确定所需的具体配置命名空间路径,则此方法非常有用。

Names::Add("server", n0);
Names::Add("server/eth0", net0);

...

Config::Set("/Names/server/eth0/TxQueue/MaxPackets", UintegerValue(25));

在这里,我们在 “/Names/” 命名空间下添加了路径元素 “server” 和 “eth0”,然后使用生成的配置路径来设置属性。

实现细节

Value Classes

StringValue、IntegerValue等类型的介绍

读者会注意到 TypeValue 类,它们是 AttributeValue 基类的子类。这些可以被视为中间类,用于从原始类型转换为属性系统使用的 AttributeValue。回想一下,这个数据库保存着序列化为字符串的多种类型的对象。可以使用中间类(例如 IntegerValue 或浮点数的 DoubleValue或通过字符串完成到此类型的转换。将类型直接隐式转换为 AttributeValue 并不实际。因此,在上面,用户可以选择使用字符串或值:

p->Set("cwnd", StringValue("100")); // string-based setter
p->Set("cwnd", IntegerValue(100)); // integer-based setter

系统提供了一些宏,可帮助用户为他们想要引入属性系统的新类型声明和定义新的 AttributeValue 子类:

  • ATTRIBUTE_HELPER_HEADER
  • ATTRIBUTE_HELPER_CPP

初始化顺序

系统中的 Attribute 不得依赖于此系统中任何其他 Attribute 的状态。这是因为系统未指定或强制执行 Attribute initialization 的顺序。这方面的一个具体示例可以在 ConfigStore 等自动化配置程序中看到。尽管给定的模型可以对其进行安排,以便以特定顺序初始化 Attributes,但另一个自动配置器可能会独立决定以字母顺序等方式更改 Attributes。由于这种非特定排序,系统中的任何 Attribute 都不能依赖于任何其他 Attribute。作为推论,Attribute setter 绝不能由于另一个 Attribute 的状态而失败。任何 Attribute setter 都不能因为更改其值而更改 (设置) 任何其他 Attribute 值。这是一个非常严格的限制,在某些情况下,必须一致地设置 Attributes 才能允许正确作。为此,我们确实允许在使用属性时进行一致性检查(参见NS_ASSERT_MSG 或 NS_ABORT_MSG)。

ConfigStore::ConfigStore()
{
  ObjectBase::ConstructSelf(AttributeConstructionList());
  // continue on with constructor.
}

请注意,该对象及其所有派生类还必须实现 GetInstanceTypeId() 方法。否则,ObjectBase::ConstructSelf() 将无法读取属性。

添加Attributes

ns-3 系统将在 attribute 系统下放置许多内部值,但毫无疑问,用户会希望将其扩展以拾取我们遗漏的值,或者将他们自己的类添加到系统中。

有三种典型的使用案例:

  • 使现有类数据成员作为 Attribute 可访问(如果尚未访问)。
  • 通过为新类提供 TypeId,使其能够将某些数据成员公开为 Attributes。
  • 为新类创建 AttributeValue 子类,以便可以作为 Attribute 访问它。
现有成员变量

考虑 TcpSocket 中的这个变量:

uint32_t m_cWnd;   // Congestion window
//假设使用 TCP 的某人想要使用元数据系统获取或设置该变量的值。如果 ns-3 尚未提供,则用户可以在运行时元数据系统中声明以下添加内容(到 TcpSocket 的 GetTypeId() 定义中):

.AddAttribute("Congestion window",
              "Tcp congestion window(bytes)",
              UintegerValue(1),
              MakeUintegerAccessor(&TcpSocket::m_cWnd),
              MakeUintegerChecker<uint16_t>())

现在,具有指向 TcpSocket 实例的指针的用户可以执行设置和获取值等作,而不必显式添加这些函数。此外,还可以应用访问控制,例如允许读取而不写入参数,或者可以应用对允许的值进行边界检查。

 New Class TypeId

新建一个class需要做什么

在这里,我们讨论对想要向 ns-3 添加新类的用户的影响。必须执行哪些其他作才能使其能够保存属性?假设我们的新类(名为 ns3::MyMobility)是一种移动模型。首先,该类应从其父类 ns3::MobilityModel 继承。在 my-mobility.h 头文件中:

namespace ns3 {

class MyMobility : public MobilityModel
{
//这需要我们声明 GetTypeId() 函数。这是一个单行公共函数声明:

public:
  /**
   *  Register this type.
   *  \return The object TypeId.
   */
  static TypeId GetTypeId();
//我们已经介绍了 TypeId 定义在 my-mobility.cc 实现文件中的样子:

NS_OBJECT_ENSURE_REGISTERED(MyMobility);

TypeId
MyMobility::GetTypeId()
{
  static TypeId tid = TypeId("ns3::MyMobility")
    .SetParent<MobilityModel>()
    .SetGroupName("Mobility")
    .AddConstructor<MyMobility>()
    .AddAttribute("Bounds",
                  "Bounds of the area to cruise.",
                  RectangleValue(Rectangle(0.0, 0.0, 100.0, 100.0)),
                  MakeRectangleAccessor(&MyMobility::m_bounds),
                  MakeRectangleChecker())
    .AddAttribute("Time",
                  "Change current direction and speed after moving for this delay.",
                  // etc (more parameters).
                  TimeValue(Seconds(1.0)),
                  MakeTimeAccessor(&MyMobility::m_modeTime),
                  MakeTimeChecker())
    ;
  return tid;
}

如果我们不想从现有类继承子类,那么在头文件中,我们只从 ns3::Object 继承,在对象文件中,我们将父类设置为 ns3::Object,并使用 .SetParent<Object>()的 Alpha Target>()中。

Typical mistakes here involve:
这里的典型错误包括:

  • 未调用 NS_OBJECT_ENSURE_REGISTERED()
  • 未调用 SetParent() 方法,或使用错误的类型调用它。
  • 未调用 AddConstructor() 方法,或使用错误的类型调用它。
  • 在其构造函数中引入 TypeId 名称的印刷错误。
  • 未使用封闭 C++ 类的完全限定 C++ 类型名称作为 TypeId 的名称。请注意,“ns3::” 是必需的

ns-3 代码库无法检测到这些错误,因此建议用户多次仔细检查他们是否正确。

新的 AttributeValue 类型

从在系统中编写新类并希望它作为属性访问的用户的角度来看,主要是编写字符串和属性值的转换的问题。其中大部分可以使用宏化代码进行复制/粘贴。例如,考虑 src/mobility/model 目录中的 Rectangle 的类声明:

//头文件
/**
 * \brief a 2d rectangle
 */
class Rectangle
{
  ...

  double xMin;
  double xMax;
  double yMin;
  double yMax;
};
//必须在类声明下方添加一个宏调用和两个运算符,以便将 Rectangle 转换为 Attribute 系统可用的值:

std::ostream &operator <<(std::ostream &os, const Rectangle &rectangle);
std::istream &operator >>(std::istream &is, Rectangle &rectangle);

ATTRIBUTE_HELPER_HEADER(Rectangle);
//实现文件
//在类定义(.cc 文件)中,代码如下所示:

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 属性的值可以存储在 ASCII 或 XML 文本文件中,并加载到将来的仿真运行中。此功能称为 ns-3 ConfigStore。ConfigStore 是属性值和默认值的专用数据库。虽然它是 src/config-store/ 目录中单独维护的模块,但我们在这里记录它,因为它唯一依赖于 ns-3 核心模块和属性。我们可以使用 中的示例来探索此系统 src/config-store/examples/config-store-save.cc 。首先,ConfigStore 的所有用户都必须包含以下语句:

#include "ns3/config-store-module.h"
//接下来,该程序添加一个示例对象 ConfigExample 来展示系统是如何扩展的:

class ConfigExample : public Object
{
public:
  static TypeId GetTypeId() {
    static TypeId tid = TypeId("ns3::A")
      .SetParent<Object>()
      .AddAttribute("TestInt16", "help text",
                    IntegerValue(-2),
                    MakeIntegerAccessor(&A::m_int16),
                    MakeIntegerChecker<int16_t>())
      ;
      return tid;
    }
  int16_t m_int16;
};

NS_OBJECT_ENSURE_REGISTERED(ConfigExample);
//接下来,我们使用 Config 子系统以两种方式覆盖默认值:

onfig::SetDefault("ns3::ConfigExample::TestInt16", IntegerValue(-5));

Ptr<ConfigExample> a_obj = CreateObject<ConfigExample>();
NS_ABORT_MSG_UNLESS(a_obj->m_int16 == -5,
                    "Cannot set ConfigExample's integer attribute via Config::SetDefault");

Ptr<ConfigExample> a2_obj = CreateObject<ConfigExample>();
a2_obj->SetAttribute("TestInt16", IntegerValue(-3));
IntegerValue iv;
a2_obj->GetAttribute("TestInt16", iv);
NS_ABORT_MSG_UNLESS(iv.Get() == -3,
                    "Cannot set ConfigExample's integer attribute via SetAttribute");

下一个语句是必要的,以确保创建的(其中一个)对象作为对象实例根于配置命名空间中。这通常发生在将对象聚合到 ns3::Node 或 ns3::Channel 实例时,但在这里,由于我们在核心级别工作,因此需要创建一个新的根命名空间对象:

Config::RegisterRootNamespaceObject(a2_obj);

Writing

能把当前仿真涉及的所有attribute都给他导出来,还可以看到默认值以及修改之后的值,很好很好,很好啊;

接下来,我们要输出配置存储。这些示例以两种格式(XML 和原始文本)演示如何执行此作。在实践中,应该在调用 Simulator::Run() 之前执行此步骤,以在运行模拟之前保存最终配置。有三个 Attribute 控制 ConfigStore 的行为:“Mode”、“Filename”和“FileFormat”。Mode(模式)(默认为 “None”)配置 ns-3 是应该从以前保存的文件加载配置(指定 “Mode=Load”)还是将其保存到文件(指定 “Mode=Save”)。文件名(默认为 “”)是 ConfigStore 应该读取或写入其数据的位置。FileFormat(默认为 “RawText”)控制 ConfigStore 格式是纯文本还是 Xml (“FileFormat=Xml”)

Config::SetDefault("ns3::ConfigStore::Filename", StringValue("output-attributes.xml"));
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("Xml"));
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Save"));
ConfigStore outputConfig;
outputConfig.ConfigureDefaults();
outputConfig.ConfigureAttributes();

// Output config store to txt format
Config::SetDefault("ns3::ConfigStore::Filename", StringValue("output-attributes.txt"));
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("RawText"));
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Save"));
ConfigStore outputConfig2;
outputConfig2.ConfigureDefaults();
outputConfig2.ConfigureAttributes();

Simulator::Run();

Simulator::Destroy();
//请注意,这些语句的位置恰好在开始 simulation 之前 (即,在所有 configuration 都已发生之后) 的值之前。运行后,您可以打开 output-attributes.txt 文件并查看:

...
default ns3::ErrorModel::IsEnabled "true"
default ns3::RateErrorModel::ErrorUnit "ERROR_UNIT_BYTE"
default ns3::RateErrorModel::ErrorRate "0"
default ns3::RateErrorModel::RanVar "ns3::UniformRandomVariable[Min=0.0|Max=1.0]"
default ns3::BurstErrorModel::ErrorRate "0"
default ns3::BurstErrorModel::BurstStart "ns3::UniformRandomVariable[Min=0.0|Max=1.0]"
default ns3::BurstErrorModel::BurstSize "ns3::UniformRandomVariable[Min=1|Max=4]"
default ns3::PacketSocket::RcvBufSize "131072"
default ns3::PcapFileWrapper::CaptureSize "65535"
default ns3::PcapFileWrapper::NanosecMode "false"
default ns3::SimpleNetDevice::PointToPointMode "false"
default ns3::SimpleNetDevice::TxQueue "ns3::DropTailQueue<Packet>"
default ns3::SimpleNetDevice::DataRate "0bps"
default ns3::PacketSocketClient::MaxPackets "100"
default ns3::PacketSocketClient::Interval "+1000000000.0ns"
default ns3::PacketSocketClient::PacketSize "1024"
default ns3::PacketSocketClient::Priority "0"
default ns3::ConfigStore::Mode "Save"
default ns3::ConfigStore::Filename "output-attributes.txt"
default ns3::ConfigStore::FileFormat "RawText"
default ns3::ConfigExample::TestInt16 "-5"
global SimulatorImplementationType "ns3::DefaultSimulatorImpl"
global SchedulerType "ns3::MapScheduler"
global RngSeed "1"
global RngRun "1"
global ChecksumEnabled "false"
value /$ns3::ConfigExample/TestInt16 "-3"

在上面,显示了核心模块和网络模块属性的几个默认值。然后,记录 ns-3 全局值的所有值。最后,显示根于配置命名空间的 ConfigExample 实例的值。在实际的 ns-3 程序中,将显示更多的模型、属性和默认值。XML 版本也存在于 output-attributes.xml 中:

Reading

上一步writing出来的选项,选择一些想修改的default,改了粘贴到另一个单独txt,在这reading一下,就都改了,很方便的

接下来,我们将讨论通过存储的输入配置文件配置仿真。与编写最终模拟配置相比,有几个关键差异。首先,我们需要在编写仿真配置语句之前,将此类语句放在程序的开头(以便在用于对象构造之前注册这些值)。

Config::SetDefault("ns3::ConfigStore::Filename", StringValue("input-defaults.xml"));
Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Load"));
Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("Xml"));
ConfigStore inputConfig;
inputConfig.ConfigureDefaults();

接下来,请注意,输入配置数据的加载仅限于 Attribute default( not instance)值和全局值。不支持属性实例值,因为在模拟的此阶段,在构造任何对象之前,周围没有此类对象实例。(请注意,配置存储的未来增强功能可能会更改此行为)。

其次,虽然 ConfigStore state 的输出将列出数据库中的所有内容,但输入文件只需要包含要覆盖的特定值。因此,使用此类进行输入文件配置的一种方法是使用上述输出 (“Save”) “Mode” 生成初始配置,从该配置文件中仅提取希望更改的元素,并将这些最小元素移动到新的配置文件中,然后可以在随后的仿真运行中安全地编辑和加载该文件。

实例化 ConfigStore 对象时,必须通过命令行或程序语句设置其属性 “Filename”、“Mode” 和 “FileFormat”。

Reading/Writing 例子

作为一个更复杂的示例,假设我们想要从名为 input-defaults.xml 的 input 文件中读取 defaults 的配置,并将结果属性写出到名为 output-attributes.xml.

#include "ns3/config-store-module.h"
...
int main(...)
{

  Config::SetDefault("ns3::ConfigStore::Filename", StringValue("input-defaults.xml"));
  Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Load"));
  Config::SetDefault("ns3::ConfigStore::FileFormat", StringValue("Xml"));
  ConfigStore inputConfig;
  inputConfig.ConfigureDefaults();

  //
  // Allow the user to override any of the defaults and the above Bind() at
  // run-time, viacommand-line arguments
  //
  CommandLine cmd;
  cmd.Parse(argc, argv);

  // setup topology
  ...

  // Invoke just before entering Simulator::Run()
  Config::SetDefault("ns3::ConfigStore::Filename", StringValue("output-attributes.xml"));
  Config::SetDefault("ns3::ConfigStore::Mode", StringValue("Save"));
  ConfigStore outputConfig;
  outputConfig.ConfigureAttributes();
  Simulator::Run();
}

ConfigStore 用例(仿真前和仿真后)

值得强调的是,ConfigStore 可以用于不同的目的,这反映在脚本中调用 ConfigStore 的位置。

The typical use-cases are:
典型的用例是:

  • 更改对象默认属性
  • 检查/更改特定对象属性
  • 检查模拟对象及其属性

事实上,模拟开始时可能会创建一些对象。因此,如果在代码中较早地调用 ConfigStore,则不会 “报告” 其属性。典型的工作流可能涉及运行模拟,在模拟结束时(在 Simulator::Run() 之后和 Simulator::D estroy() 之前)调用 ConfigStore,这将显示对象中的所有属性,包括具有默认值的属性和在模拟执行期间更改值的属性。要更改这些值,您需要更改默认(类范围的)属性值(在本例中,在创建对象之前调用 ConfigStore)或特定对象属性(在本例中,在创建对象后调用 ConfigStore,通常在 Simulator::Run() 之前)。

ConfigStore GUI

这个挺好用的,可以改,改了就按照新改的跑

ConfigStore 有一个基于 GTK 的前端。这允许用户使用 GUI 来访问和更改变量。此处提供了一些屏幕截图。它们是在 Simulator::Run() src/lte/examples/lena-dual-stripe.cc 之后使用 GtkConfig 的结果。

RealTime

如果想和外面通信,或者外面走1s,模拟器走1s,就需要这个

NS-3 专为集成到 TestBed 和虚拟机环境中而设计。要与实际网络堆栈集成并发出/使用数据包,需要一个实时调度器来尝试将仿真时钟与硬件时钟锁定。我们在这里描述了一个组件:RealTime 调度器。realtime scheduler 的目的是使 simulation clock 的进度相对于某些外部 time base 同步发生。如果没有外部时基(挂钟),模拟时间会立即从一个模拟时间跳到下一个模拟时间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值