ns-3的代码设计
ns-3的核心是时间调度和网络模拟的支持,在工程技术上有很多重要的设计值得了解。
核心模块
ns-3的内核指的就是 src/core/下的代码,而基于core定义的network模块给出了最基本的单元定义如packet。围绕这两个模块,可以将ns-3的结构描述为:
随机数
随机数生成器RNG的功能:
- 提供一个随机数序列(循环序列);
- 可以被分隔成几个相互独立的数据流。
我们总是希望一个RNG能提供一个非常长的循环序列,并有效地分配每一个数据流。
ns-3使用 MRG32k3a 生成器,保证足够的循环长度以及数据流数量。
一个生成随机数的例子:
#include "ns3/simulator.h"
#include "ns3/nstime.h"
#include "ns3/command-line.h"
#include "ns3/rng-seed-manager.h"
#include "ns3/random-vatiable-stream.h"
#include <iostream>
using namespace ns3;
int main(int argc, char *argv[])
{
CommandLine cmd;
cmd.Parse(argc, argv);
double min = 0.0;
double max = 10.0;
// 改变种子后会得到不同的随机数
// 如果不改变种子同时运行多组实验,可以通过SetRun()来设置组数保证实验的独立性
RngSeedManager::SetSeed(1);
Ptr<UniformRandomVariable> uv = CreateObject<UniformRandomVariable>();
uv->SetAttribute("Min", DoubleValue(min));
uv->SetAttribute("Max", DoubleValue(max));
std::out << uv->GetValue() << std::endl;
return 0;
}
回调机制
针对静态函数
//...
static double
CbOne(double a, double b)
{
std::out << "invoke cbOne a=" << ", b=" << b << std::endl;
return a;
}
int main(int argc, char* argv[])
{
// 声明了一个叫“one”的回调,指定了返回值和两个参数都是double
// 绑定CbOne为回调函数
Callback<double, double, double> one = MakeCallback(&CbOne);
// ...
NS_ASSERT(!one.IsNull());
double retOne = one(10.1, 11.2);
}
针对类成员函数
class MyCb{
public:
int CbTwo(double a){
std::out << "invoke cbTwo a=" << a << std::endl;
}
return -5;
}
MyCb cb;
Callback<int, double> two = MakeCallback(&MyCb::CbTwo, &cb);
NS_ASSERT(!two.IsNull());
int retTwo = two(10.0);
构建Null回调的方法
MakeNullCallback<int, double>();
Attribute系统
从下面的例子及注释中可以看清Attribute的用法
#include "ns3/log.h"
#include "ns3/command-line.h"
#include "ns3/ptr.h"
#include "ns3/config.h"
#include "ns3/uinteger.h"
#include "ns3/string.h"
#include "ns3/pointer.h"
#include "ns3/simulator.h"
#include "ns3/node.h"
#include "ns3/queue.h"
#include "ns3/drop-tail-queue.h"
#include "ns3/point-to-point-net-device.h"
using namespace ns3;
int main(int argc, char *argv[])
{
LogComponentEnable("AttributeValueSample", LOG_LEVEL_INFO);
// 1. 设置默认值
Config::SetDefault("ns3::DropTailQueue::MaxPackets", StringValue("80"));
Config::SetDefault("ns3::DropTailQueue::MaxPackets", UintegerValue("80"));
CommandLine cmd;
cmd.Parse(argc, argv);
// 这里体现了ns-3使用模板创建对象的方法 CreateObject
// Ptr是ns-3提供的计数指针
Ptr<Node> n0 = CreateObject<Node>();
Ptr<PointToPointNetDevice> net0 = CreateObject<PointToPointNetDevice>();
n0->AddDevice(net0);
// 为node分配一个packet队列,它从尾部开始丢弃packet
Ptr<Queue> q = CreateObject<DropTailQueue>();
net0->SetQueue(q);
// ptr是一个指针变量,然后把net0的TxQueue赋值给ptr
PointerValue ptr;
// 2. 通过指针获取属性值
net0->GetAttribute("TxQueue", ptr);
Ptr<Queue> txQueue = ptr.Get<Queue>();
Ptr<DropTailQueue> dtq = txQueue->GetQueue<DropTailQueue>();
NS_ASSERT(dtq);
UintegerValue limit;
dtq->GetAttribute("MaxPackets", limit);
NS_LOG_INFO("1. dtq limit: " << limit.Get() << " packets");
txQueue->GetAttribute("MaxPackets", limit);
NS_LOG_INFO("2. txQueue limit: " << limit.Get() << " packets");
txQueue->SetAttribute("MaxPackets", UintegerValue(60));
txQueue->GetAttribute("MaxPackets", limit);
NS_LOG_INFO("3. txQueue limit changed: " << limit.Get() << " packets");
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");
Simulator::Destory();
}
除了上述两个方法,还有添加属性条目的方法:AddAttribute()
对于类TcpSocket的一个成员变量 uint32_tm_cWnd,假设使用TCP模块时想要试用元数据获取或设置该变量的值,如果ns-3还没有提供这个变量,那么用户可以在元数据系统中添加如下声明:
ptr->AddAttribute("Congestion window", "Tcp congestion window (bytes)",
Uintergevalue(1),
MakeUintegerAccessor(&TcpSocket::m_cWnd),
MakeUintegerChecker<uint16_t>())
Tracing系统
拆解一个Tracing系统,其组成可以理解为:TracingSource、TracingSink以及关联它们的方法。
通过一个例子了解:
#include "ns3/object.h"
#include "ns3/uinteger.h"
#include "ns3/traced-value.h"
#include "ns3/trace-source-accessor.h"
#include <iostream>
using namespace ns3;
// Tracing系统和Attribute系统密切相关,
// 所以每一个要trace的数据都必须属于一个特定的类。
class MyObject : public Object
{
public:
static TypeId GetTypeId(void)
{
static TypeId tid = TypeId("MyObject")
.SetParent(Object::GetTypeId())
.AddConstructor<MyObject>()
.AddTraceSourceAccessor(&MyObject::m_myInt);
// 此处的m_myInt被确定为一个TracingSource
return tid;
}
MyObject(){}
TraceValue<uint32_t> m_myInt;
}
// 函数IntTrace就是TraceSink
void IntTrace(int oldValue, int newValue)
{
std::out << "Traced " << oldValue << " to " << newValue << std::endl;
}
int main(int argc, char *argv[])
{
Ptr<MyObject> myObject = CreateObject<MyObject>();
// 这个函数将Source和Sink关联起来
// 当myObject.m_myInt改变时,IntTrace才会被调用
myObject->TraceConnectWithoutContext("MyInteger", MakeCallback(&IntTrace));
myObject->m_myInt = 1234;
}
但是TraceConnectWithoutContext这样的函数很少被使用。
通常我们使用一个被叫作“Config Path”的子系统。
如何关联TraceSource和TraceSink
下面演示了如何使用Config关联TraceSource和TraceSink
using namespace std;
// 这个函数就是TraceSink
void CourseChange(string context, Ptr<const MobilityModel> model)
{
Vector posision = model->GetPosition();
NS_LOG_UNCOND(context << " x = " << position.x << " y = " << position.y);
}
// ...
ostringstream oss;
// 类MobilityModel的属性CourseChange是TraceSource
oss << "/NodeList/" << wifiStaNodes.Get(nWifi-1)->GetId() << "/$ns3::MobilityModel/CourseChange";
Config::Connect(oss.str(), MakeCallback(&CourseChange));
oss.str()其实就是“ConfigPath”,起到了类似绑定的作用,每当MobilityModel的位置/速度的值变了就会进行trace。
如何确定TraceSource
所有可用的TraceSource在文档: ns-3: All TraceSources
如何确定TraceSink
其实就是定义一个函数并将其设置为回调。
该函数的确定步骤如下:
- 返回值为 void
- 参数列表的参数类型是 TraceSource 的变量类型
- 要是用 Config::Connect,那么参数列表的第一个参数还得加一个字符串参数