ns-3和NDN代码分析(4) ndn-simple进一步剖析
前面的文章中,我们剖析了 ndn-simple.cpp
的每一行代码的大致功能。在这篇文章中,我将跳每一行代码的子函数里,对于每个子函数进行更加细致地剖析,用于分析每一行代码的工作机理。
examples之ndn-simple.cpp每个函数逐行剖析
1 | // ndn-simple.cpp |
首先看一下Simulator:
整个仿真的核心就在这个 Simulator
上了。其实总的来说,我们可以这么认为:前面的代码是在给 Simulator
配置一些 Config
或者 Event
,到了最后一起执行。
那么以 Simulator::Run()
为例,它是怎么实现的呢?其实它把任务推给了 GetImpl()
返回的 SimulatorImpl *
了。
1 | void |
那么 SimulatorImpl
里是啥玩意?
1 | class SimulatorImpl : public Object |
既然是纯虚函数,也就是说这个 Simulator
一定是派生类的对象,只是被强制转化为了纯虚基类 SimulatorImpl
。那么,这究竟是那一个派生类呢?经过我的侦察,发现 GetImpl()
里的 g_simTypeImpl
有玄机:
1 | static GlobalValue g_simTypeImpl = GlobalValue |
说白了,我们以后看到 Simulator
就可以脑补为 DefaultSimulatorImpl
了。这个文件的位置在 ~/ndnSIM/ns-3/src/core/model/default-simulator-impl.cc
。
至于 Run()
函数,看一看里面的实现就明白了,就是一直执行 ProcessOneEvent
,这个函数对每个 m_events
的 next
事件执行 next.impl->Invoke()
。
1 | void |
而事件是由谁添加的呢?其实是由 ProcessEventsWithContext
函数添加的。
1 | DefaultSimulatorImpl::ProcessEventsWithContext (void) |
1、Config::SetDefault
代码如下
1 | Config::SetDefault("ns3::PointToPointNetDevice::DataRate", StringValue("1Mbps")); |
首先,第一行代码从字面意思就能看出来:是用于配置点对点设备的数据速率为 1Mbps
。那么它具体是如何工作的呢?
总的来说,就是把第一个参数按照最后一个 ::
进行切割,得到 "tidName = ns3::PointToPointNetDevice"
和 paramName = "DataRate"
。而 ns3::IidManager
类的 m_namemap
里,储存了 tidName
到 uid
的映射,每个 uid
唯一表示了一个内容,所以我们就找到的这个待操作的的类。对于这个类,我们只需要遍历它的 attributes
,如果匹配且输入值合法,就可以执行 SetAttributeInitialValue
函数。
1 | bool SetDefaultFailSafe (std::string fullName, const AttributeValue &value) |
为了与 SetDefault
对应,每个类都配备有相应的 GetTypeId
函数。以 Producer
类为例,我们可以通过 "ns3::ndn::Producer"
来找到这个类,并且可以访问、初始化这个类的所有被 AddAttribute
的变量。
1 | TypeId |
2、CommandLine::Parse
这个其实就是解析 cmd
的参数,首先按照分割符进行 Split
,对于其中的参数用 HandleOption
或者 HandleNonOption
进行解析。
举个例子 --abc=efg
或者 -abc=efg
,用 HandleOption
解析出来就是 name="abc"
, value="efg"
,然后执行 HandleArgument(name, value)
。 --vis
解析出来就是 name="vis"
, value=""
。
void
CommandLine::Parse (std::vector
{
NS_LOG_FUNCTION (this << args.size () << args);
m_nonOptionCount = 0;
m_name = “”;
if (args.size () > 0)
{
m_name = SystemPath::Split (args[0]).back ();
args.erase (args.begin ()); // discard the program name
1 | void |
3、NodeContainer::Create
这个就是创建 N 个节点,看代码一目了然——
1 | void |
4、PointToPointHelper
这玩意还挺有意思的,它的构造函数是
1 | PointToPointHelper::PointToPointHelper () |
这个类是点对点的助手类,比如 Install
函数可以执行节点之间网络拓扑的安装。以代码 p2p.Install(nodes.Get(0), nodes.Get(1));
为例进行分析。
该函数给两个节点分配了具有唯一 MAC
地址的 PointToPointNetDevice
对象进行管理,并为它们创建信道、传数据包的队列 Queue<Packet>
。
5、ndn::StackHelper
这个构造函数也挺好玩的,配置了一堆策略。其中 PointToPointNetDeviceCallback
很有意思,它用于配置点对点网络对象,创建了 GenericLinkService
和 NetDeviceTransport
的链路层、传输层管理器,并创建了一个 Face
来管理它们,后续的所有发包和收包都靠它。
1 | StackHelper::StackHelper() |
主要是 ndnHelper.InstallAll();
这个函数,它用 Simulator::ScheduleWithContext
函数为每一个 Node
都在 Simulator
里添加了一个 StackHelper::doInstall
事件。
而 doInstall
则是让 Node
聚合 L3Protocol
,并为 Node
执行 createAndRegisterFace
,调用了 PointToPointNetDeviceCallback
创建了一个管理 GenericLinkService
和 NetDeviceTransport
的接口 Face
。
6、ndn::StrategyChoiceHelper::InstallAll
ndn::StrategyChoiceHelper::InstallAll("/prefix", "/localhost/nfd/strategy/multicast");
这一行代码,就是对 NodeContainer
里的每一个 Node
都执行一次 StrategyChoiceHelper::Install
。
1 | void |
其实内容其实很简单,就是把 StrategyChoiceHelper::sendCommand
添加到仿真器的事件,和上面的 ndn::StackHelper
分析方法是类似的,这里还是贴出源代码
1 | void |
其中 sendCommand
函数的作用是为当前节点添加 parameters
。
7、ndn::AppHelper
生产者和消费者都是由 ndn::AppHelper
来管理。
1 | // Consumer |
ndn::AppHelper
函数的构造函数是将参数字符串作为 TypeId
。
1 | AppHelper::AppHelper(const std::string& app) |
例如这里 consumerHelper
就是设置 TypeId = "ns3::ndn::ConsumerCbr"
,前缀为 "/prefix"
,频率为 "10"
,将这些设置都存在 m_factory
里。
然后把这个 consumerHelper
的 m_factory
安装到 nodes.Get(0)
上,这个安装函数也是调用了 Simulator
的 ScheduleWithContext
函数设置了 Application::Initialize
事件,该事件对 Node0
进行初始化配置,从而使得 Node0
被配置为 ConsumerCbr
。
当然, Producer
的配置方法也是同理。总之就是用了一个AppHelper设置好配置信息,将配置事件塞到事件队列里,等到仿真开始时执行。
8、ns3::Simulator
说白了,前面的操作都是一堆配置。配置完干什么?当然是塞到仿真器的事件列表里,等 Simulator::Run()
啊!
Simulator::Stop(Seconds(20.0))
是设置了20s
后的事件,这个事件令m_stop = false
。Simulator::Run()
是执行m_eventsWithContext
和m_events
里的所有事件,直到事件空了,或者达到m_stop
了。(m_eventsWithContext
可以理解为m_event
的缓冲队列,真正执行的是m_event
,然后每次都从m_eventsWithContext
抓出一个塞到m_event
里)Simulator::Destroy()
是执行m_destroyEvents
里的所有事件。
1 | void |
总结与分析
总结一下整个 ndn-simple.cpp
的仿真流程,我们可以总结出整个 ns3
软件的仿真过程。
首先 ns3
的仿真流程由仿真器 Simulator
所控制,而代码的总体流程则是分为:
- 设置配置选项
- 将事件配置到仿真器的事件队列中
- 使用仿真器执行事件队列中的事件
具体而言——
- 其中首先要有基础配置、用
NodeContainer
执行节点的创建,这些(特别是节点本身)是后面网络拓扑、策略配置的基础。 - 然后用
PointToPointHelper
执行网络拓扑的构建,用ndn::StackHelper
执行路由链路层、传输层接口的安装,用ndn::StrategyChoiceHelper
执行转发策略配置。 - 接着用
ndn::AppHelper
执行生产者和消费者的配置。这里要注意,我们不是创建了一个生产者或者消费者,而是将原本就存在的节点设置为生产者或者消费者。 - 最后,用
Simulator
执行仿真。其中Simulator
调用了SimulatorImpl
类的实现,而这是纯虚类,其中真正调用的是DefaultSimulatorImpl
类。