文章目录

  • 前言
  • 源代码
  • 0、先聊聊Simulator
  • 1、Config::SetDefault
  • 2、CommandLine::Parse
  • 3、NodeContainer::Create
  • 4、PointToPointHelper
  • 5、ndn::StackHelper
  • 6、ndn::StrategyChoiceHelper::InstallAll
  • 7、ndn::AppHelper
  • 8、ns3::Simulator
  • 总结与分析

前言

前面的文章ndnSIM学习(四)——examples之ndn-simple.cpp超详细剖析中,我们剖析了 ndn-simple.cpp 的每一行代码的大致功能。在这篇文章中,我将跳每一行代码的子函数里,对于每个子函数进行更加细致地剖析,用于分析每一行代码的工作机理。

源代码

把代码还是再复制一遍

// ndn-simple.cpp#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/ndnSIM-module.h"namespace ns3 {int
main(int argc, char* argv[])
{// setting default parameters for PointToPoint links and channelsConfig::SetDefault("ns3::PointToPointNetDevice::DataRate", StringValue("1Mbps"));Config::SetDefault("ns3::PointToPointChannel::Delay", StringValue("10ms"));Config::SetDefault("ns3::QueueBase::MaxSize", StringValue("20p"));// Read optional command-line parameters (e.g., enable visualizer with ./waf --run=<> --visualizeCommandLine cmd;cmd.Parse(argc, argv);// Creating nodesNodeContainer nodes;nodes.Create(3);// Connecting nodes using two linksPointToPointHelper p2p;p2p.Install(nodes.Get(0), nodes.Get(1));p2p.Install(nodes.Get(1), nodes.Get(2));// Install NDN stack on all nodesndn::StackHelper ndnHelper;ndnHelper.SetDefaultRoutes(true);ndnHelper.InstallAll();// Choosing forwarding strategyndn::StrategyChoiceHelper::InstallAll("/prefix", "/localhost/nfd/strategy/multicast");// Installing applications// Consumerndn::AppHelper consumerHelper("ns3::ndn::ConsumerCbr");// Consumer will request /prefix/0, /prefix/1, ...consumerHelper.SetPrefix("/prefix");consumerHelper.SetAttribute("Frequency", StringValue("10")); // 10 interests a secondauto apps = consumerHelper.Install(nodes.Get(0));                        // first nodeapps.Stop(Seconds(10.0)); // stop the consumer app at 10 seconds mark// Producerndn::AppHelper producerHelper("ns3::ndn::Producer");// Producer will reply to all requests starting with /prefixproducerHelper.SetPrefix("/prefix");producerHelper.SetAttribute("PayloadSize", StringValue("1024"));producerHelper.Install(nodes.Get(2)); // last nodeSimulator::Stop(Seconds(20.0));Simulator::Run();Simulator::Destroy();return 0;
}} // namespace ns3int
main(int argc, char* argv[])
{return ns3::main(argc, argv);
}

0、先聊聊Simulator

整个仿真的核心就在这个 Simulator 上了。其实总的来说,我们可以这么认为:前面的代码是在给 Simulator 配置一些 Config 或者 Event ,到了最后一起执行。

那么以 Simulator::Run() 为例,它是怎么实现的呢?点开一看,瞬间就惊了。好家伙,这家伙搁着玩踢皮球呢,把任务推给了 GetImpl() 返回的 SimulatorImpl * 了。

void
Simulator::Run (void)
{NS_LOG_FUNCTION_NOARGS ();Time::ClearMarkedTimes ();GetImpl ()->Run ();
}

那么 SimulatorImpl 里是啥玩意?不看不知道,一看吓一跳。我超!几乎全是纯虚函数。

class SimulatorImpl : public Object
{public:static TypeId GetTypeId (void);virtual void Destroy () = 0;virtual bool IsFinished (void) const = 0;virtual void Stop (void) = 0;virtual void Stop (const Time &delay) = 0;virtual EventId Schedule (const Time &delay, EventImpl *event) = 0;virtual void ScheduleWithContext (uint32_t context, const Time &delay, EventImpl *event) = 0;virtual EventId ScheduleNow (EventImpl *event) = 0;virtual EventId ScheduleDestroy (EventImpl *event) = 0;virtual void Remove (const EventId &id) = 0;virtual void Cancel (const EventId &id) = 0;virtual bool IsExpired (const EventId &id) const = 0;virtual void Run (void) = 0;virtual Time Now (void) const = 0;virtual Time GetDelayLeft (const EventId &id) const = 0;virtual Time GetMaximumSimulationTime (void) const = 0;virtual void SetScheduler (ObjectFactory schedulerFactory) = 0;virtual uint32_t GetSystemId () const = 0; virtual uint32_t GetContext (void) const = 0;virtual uint64_t GetEventCount (void) const = 0;
};

既然是纯虚函数,也就是说这个 Simulator 一定是派生类的对象,只是被强制转化为了纯虚基类 SimulatorImpl 。那么,这究竟是那一个派生类呢?经过我的侦察,发现 GetImpl() 里的 g_simTypeImpl 有玄机:

static GlobalValue g_simTypeImpl = GlobalValue("SimulatorImplementationType","The object class to use as the simulator implementation",StringValue ("ns3::DefaultSimulatorImpl"),MakeStringChecker ());

说白了,我们以后看到 Simulator 就可以脑补为 DefaultSimulatorImpl 了。这个文件的位置在 ~/ndnSIM/ns-3/src/core/model/default-simulator-impl.cc

至于 Run() 函数,看一看里面的实现就明白了,就是一直执行 ProcessOneEvent ,这个函数对每个 m_eventsnext 事件执行 next.impl->Invoke()

void
DefaultSimulatorImpl::Run (void)
{NS_LOG_FUNCTION (this);// Set the current threadId as the main threadIdm_main = SystemThread::Self();ProcessEventsWithContext ();m_stop = false;while (!m_events->IsEmpty () && !m_stop) {ProcessOneEvent ();}NS_ASSERT (!m_events->IsEmpty () || m_unscheduledEvents == 0);
}

而事件是由谁添加的呢?其实是由 ScheduleWithContext 函数添加的。

void
DefaultSimulatorImpl::ScheduleWithContext (uint32_t context, Time const &delay, EventImpl *event)
{NS_LOG_FUNCTION (this << context << delay.GetTimeStep () << event);if (SystemThread::Equals (m_main)){Time tAbsolute = delay + TimeStep (m_currentTs);Scheduler::Event ev;ev.impl = event;ev.key.m_ts = (uint64_t) tAbsolute.GetTimeStep ();ev.key.m_context = context;ev.key.m_uid = m_uid;m_uid++;m_unscheduledEvents++;m_events->Insert (ev);}else{EventWithContext ev;ev.context = context;// Current time added in ProcessEventsWithContext()ev.timestamp = delay.GetTimeStep ();ev.event = event;{CriticalSection cs (m_eventsWithContextMutex);m_eventsWithContext.push_back(ev);m_eventsWithContextEmpty = false;}}
}

1、Config::SetDefault

代码如下

Config::SetDefault("ns3::PointToPointNetDevice::DataRate", StringValue("1Mbps"));

首先,第一行代码从字面意思就能看出来:是用于配置点对点设备的数据速率为 1Mbps 。那么它具体是如何工作的呢?

总的来说,就是把第一个参数按照最后一个 :: 进行切割,得到 "tidName = ns3::PointToPointNetDevice"paramName = "DataRate" 。而 ns3::IidManager 类的 m_namemap 里,储存了 tidNameuid 的映射,每个 uid 唯一表示了一个内容,所以我们就找到的这个待操作的的类。对于这个类,我们只需要遍历它的 attributes ,如果匹配且输入值合法,就可以执行 SetAttributeInitialValue 函数。

为了与 SetDefault 对应,每个类都配备有相应的 GetTypeId 函数。以 Producer 类为例,我们可以通过 "ns3::ndn::Producer" 来找到这个类,并且可以访问、初始化这个类的所有被 AddAttribute 的变量。

TypeId
Producer::GetTypeId(void)
{static TypeId tid =TypeId("ns3::ndn::Producer").SetGroupName("Ndn").SetParent<App>().AddConstructor<Producer>().AddAttribute("Prefix", "Prefix, for which producer has the data", StringValue("/"),MakeNameAccessor(&Producer::m_prefix), MakeNameChecker()).AddAttribute("Postfix","Postfix that is added to the output data (e.g., for adding producer-uniqueness)",StringValue("/"), MakeNameAccessor(&Producer::m_postfix), MakeNameChecker()).AddAttribute("PayloadSize", "Virtual payload size for Content packets", UintegerValue(1024),MakeUintegerAccessor(&Producer::m_virtualPayloadSize),MakeUintegerChecker<uint32_t>()).AddAttribute("Freshness", "Freshness of data packets, if 0, then unlimited freshness",TimeValue(Seconds(0)), MakeTimeAccessor(&Producer::m_freshness),MakeTimeChecker()).AddAttribute("Signature","Fake signature, 0 valid signature (default), other values application-specific",UintegerValue(0), MakeUintegerAccessor(&Producer::m_signature),MakeUintegerChecker<uint32_t>()).AddAttribute("KeyLocator","Name to be used for key locator.  If root, then key locator is not used",NameValue(), MakeNameAccessor(&Producer::m_keyLocator), MakeNameChecker());return tid;
}

2、CommandLine::Parse

这个其实就是解析 cmd 的参数,首先按照分割符进行 Split ,对于其中的参数用 HandleOption 或者 HandleNonOption 进行解析。

举个例子 --abc=efg 或者 -abc=efg ,用 HandleOption 解析出来就是 name="abc"value="efg" ,然后执行 HandleArgument(name, value)--vis 解析出来就是 name="vis"value=""

3、NodeContainer::Create

这个就是创建 NNN 个节点,看代码一目了然——

void
NodeContainer::Create (uint32_t n)
{for (uint32_t i = 0; i < n; i++){m_nodes.push_back (CreateObject<Node> ());}
}

4、PointToPointHelper

这玩意还挺有意思的,它的构造函数是

PointToPointHelper::PointToPointHelper ()
{m_queueFactory.SetTypeId ("ns3::DropTailQueue<Packet>");m_deviceFactory.SetTypeId ("ns3::PointToPointNetDevice");m_channelFactory.SetTypeId ("ns3::PointToPointChannel");m_remoteChannelFactory.SetTypeId ("ns3::PointToPointRemoteChannel");
}

这个类是点对点的助手类,比如 Install 函数可以执行节点之间网络拓扑的安装。以代码 p2p.Install(nodes.Get(0), nodes.Get(1)); 为例进行分析。

该函数给两个节点分配了具有唯一 MAC 地址的 PointToPointNetDevice 对象进行管理,并为它们创建信道、传数据包的队列 Queue<Packet>

5、ndn::StackHelper

这个构造函数也挺好玩的,配置了一堆策略。其中 PointToPointNetDeviceCallback 很有意思,它用于配置点对点网络对象,创建了 GenericLinkServiceNetDeviceTransport 的链路层、传输层管理器,并创建了一个 Face 来管理它们,后续的所有发包和收包都靠它。

StackHelper::StackHelper(): m_isForwarderStatusManagerDisabled(false), m_isStrategyChoiceManagerDisabled(false), m_needSetDefaultRoutes(false)
{setCustomNdnCxxClocks();m_csPolicies.insert({"nfd::cs::lru", [] { return make_unique<nfd::cs::LruPolicy>(); }});m_csPolicies.insert({"nfd::cs::priority_fifo", [] () { return make_unique<nfd::cs::PriorityFifoPolicy>(); }});m_csPolicyCreationFunc = m_csPolicies["nfd::cs::lru"];m_ndnFactory.SetTypeId("ns3::ndn::L3Protocol");m_netDeviceCallbacks.push_back(std::make_pair(PointToPointNetDevice::GetTypeId(),MakeCallback(&StackHelper::PointToPointNetDeviceCallback, this)));// default callback will be fired if non of others callbacks fit or did the job
}

主要是 ndnHelper.InstallAll(); 这个函数,它用 Simulator::ScheduleWithContext 函数为每一个 Node 都在 Simulator 里添加了一个 StackHelper::doInstall 事件。

doInstall 则是让 Node 聚合 L3Protocol ,并为 Node 执行 createAndRegisterFace ,调用了 PointToPointNetDeviceCallback 创建了一个管理 GenericLinkServiceNetDeviceTransport 的接口 Face

6、ndn::StrategyChoiceHelper::InstallAll

ndn::StrategyChoiceHelper::InstallAll("/prefix", "/localhost/nfd/strategy/multicast"); 这一行代码,就是对 NodeContainer 里的每一个 Node 都执行一次 StrategyChoiceHelper::Install

void
StrategyChoiceHelper::InstallAll(const Name& namePrefix, const Name& strategy)
{Install(NodeContainer::GetGlobal(), namePrefix, strategy);
}

其实内容其实很简单,就是把 StrategyChoiceHelper::sendCommand 添加到仿真器的事件,和上面的 ndn::StackHelper 分析方法是类似的,这里还是贴出源代码

void
StrategyChoiceHelper::Install(Ptr<Node> node, const Name& namePrefix, const Name& strategy)
{ControlParameters parameters;parameters.setName(namePrefix);NS_LOG_DEBUG("Node ID: " << node->GetId() << " with forwarding strategy " << strategy);parameters.setStrategy(strategy);Simulator::ScheduleWithContext(node->GetId(), Seconds(0),&StrategyChoiceHelper::sendCommand, parameters, node);StackHelper::ProcessWarmupEvents();
}

其中 sendCommand 函数的作用是为当前节点添加 parameters

7、ndn::AppHelper

生产者和消费者都是由 ndn::AppHelper 来管理。

  // Consumerndn::AppHelper consumerHelper("ns3::ndn::ConsumerCbr");// Consumer will request /prefix/0, /prefix/1, ...consumerHelper.SetPrefix("/prefix");consumerHelper.SetAttribute("Frequency", StringValue("10")); // 10 interests a secondauto apps = consumerHelper.Install(nodes.Get(0));                        // first nodeapps.Stop(Seconds(10.0)); // stop the consumer app at 10 seconds mark// Producerndn::AppHelper producerHelper("ns3::ndn::Producer");// Producer will reply to all requests starting with /prefixproducerHelper.SetPrefix("/prefix");producerHelper.SetAttribute("PayloadSize", StringValue("1024"));producerHelper.Install(nodes.Get(2)); // last node

ndn::AppHelper 函数的构造函数是将参数字符串作为 TypeId

AppHelper::AppHelper(const std::string& app)
{m_factory.SetTypeId(app);
}

例如这里 consumerHelper 就是设置 TypeId = "ns3::ndn::ConsumerCbr" ,前缀为 "/prefix" ,频率为 "10" ,将这些设置都存在 m_factory 里。

然后把这个 consumerHelperm_factory 安装到 nodes.Get(0) 上,这个安装函数也是调用了 SimulatorScheduleWithContext 函数设置了 Application::Initialize 事件,该事件对 Node0 进行初始化配置,从而使得 Node0 被配置为 ConsumerCbr

当然, Producer 的配置方法也是同理。总之就是用了一个AppHelper设置好配置信息,将配置事件塞到事件队列里,等到仿真开始时执行。

8、ns3::Simulator

说白了,前面的操作都是一堆配置。配置完干什么?当然是塞到仿真器的事件列表里,等 Simulator::Run() 啊!

  • Simulator::Stop(Seconds(20.0)) 是设置了 20s 后的事件,这个事件令 m_stop = false
  • Simulator::Run() 是执行 m_eventsWithContextm_events 里的所有事件,直到事件空了,或者达到 m_stop 了。( m_eventsWithContext 可以理解为 m_event 的缓冲队列,真正执行的是 m_event ,然后每次都从 m_eventsWithContext 抓出一个塞到 m_event 里)
  • Simulator::Destroy() 是执行 m_destroyEvents 里的所有事件。
void
DefaultSimulatorImpl::Run (void)
{NS_LOG_FUNCTION (this);// Set the current threadId as the main threadIdm_main = SystemThread::Self();ProcessEventsWithContext ();m_stop = false;while (!m_events->IsEmpty () && !m_stop) {ProcessOneEvent ();}// If the simulator stopped naturally by lack of events, make a// consistency test to check that we didn't lose any events along the way.NS_ASSERT (!m_events->IsEmpty () || m_unscheduledEvents == 0);
}

总结与分析

总结一下整个 ndn-simple.cpp 的仿真流程,我们可以总结出整个 ns3 软件的仿真过程。

首先 ns3 的仿真流程由仿真器 Simulator 所控制,而代码的总体流程则是分为:

  1. 设置配置选项
  2. 将事件配置到仿真器的事件队列中
  3. 使用仿真器执行事件队列中的事件

具体而言——

  • 其中首先要有基础配置、用 NodeContainer 执行节点的创建,这些(特别是节点本身)是后面网络拓扑、策略配置的基础。
  • 然后用 PointToPointHelper 执行网络拓扑的构建,用 ndn::StackHelper 执行路由链路层、传输层接口的安装,用 ndn::StrategyChoiceHelper 执行转发策略配置。
  • 接着用 ndn::AppHelper 执行生产者和消费者的配置。这里要注意,我们不是创建了一个生产者或者消费者,而是将原本就存在的节点设置为生产者或者消费者。
  • 最后,用 Simulator 执行仿真。其中 Simulator 调用了 SimulatorImpl 类的实现,而这是纯虚类,其中真正调用的是 DefaultSimulatorImpl 类。

在这一节中,我们理解到了:ns3的仿真器是靠事件在推动仿真的进行。下一节中我们将剖析生产者与消费者,理解从消费者发出 interest 到生产者返回 data 这一路上,生产者和消费者们都做了些什么。

ndnSIM学习(八)——examples之ndn-simple.cpp每个函数逐行剖析相关推荐

  1. ndnSIM学习(四)——examples之ndn-simple.cpp超详细剖析

    文章目录 前言 代码分析 0.头文件 作用 详细解析 1.配置网络初始参数 作用 详细解析 2.命令行参数解析 作用 3.创建3个网络节点 作用 详细解析 4.定义网络拓扑结构 作用 详细解析 5.将 ...

  2. ndnSIM学习(十)——apps之ndn-producer.cpp和ndn-consumer.cpp源码分析

    文章目录 前言 ndn-producer.hpp ndn-consumer.hpp ndn-consumer-cbr.hpp 其他消费者 前言 在前面的文章ndnSIM学习(四)--examples之 ...

  3. 方差 标准差_方差与标准差——杭州市初中数学核心组寒假微课学习八年级第38课...

    国家正值非常时期,开学已经推迟,为响应"在推迟开学时段,指导各地各校充分利用'互联网+'的模式共享优质教育资源,开展远程教育教学活动和学生课业辅导,努力实现我市广大中小学校学'停课不停学', ...

  4. OpenCV与图像处理学习八——图像边缘提取(Canny检测代码)

    OpenCV与图像处理学习八--图像边缘提取(Canny检测代码) 一.图像梯度 1.1 梯度 1.2 图像梯度 二.梯度图与梯度算子 2.1模板卷积 2.2 梯度图 2.3 梯度算子 2.3.1 R ...

  5. PyTorch框架学习八——PyTorch数据读取机制(简述)

    PyTorch框架学习八--PyTorch数据读取机制(简述) 一.数据 二.DataLoader与Dataset 1.torch.utils.data.DataLoader 2.torch.util ...

  6. JMS学习八(ActiveMQ消息持久化)

    JMS学习八(ActiveMQ消息持久化) ActiveMQ的消息持久化机制有JDBC,AMQ,KahaDB和LevelDB,还有一种内存存储的方式,由于内存不属于持久化范畴,而且如果使用内存队列,可 ...

  7. H323plus的学习使用(2)——simple实现音视频通话

    H323plus的学习使用(2)--simple实现音视频通话 但该文章的解释大多不够详细,而且使用的版本不同,编译安装的环境也不同,导致了文章中所说的解决方法并不能够十分完美地解决,本人在按照该文章 ...

  8. 推荐系统遇上深度学习(八十七)-[阿里]基于搜索的用户终身行为序列建模

    本文介绍的论文是<Search-based User Interest Modeling with Lifelong Sequential Behavior Data for Click-Thr ...

  9. [Java并发包学习八]深度剖析ConcurrentHashMap

    转载----http://qifuguang.me/2015/09/10/[Java并发包学习八]深度剖析ConcurrentHashMap/ HashMap是非线程安全的,并发情况下使用,可能会导致 ...

最新文章

  1. python面向对象编程 Object-Oriented
  2. 主从同步出现一下错误:Slave_IO_Running: Connecting
  3. 重做实验七 寻址方式在结构化数据访问中的应用
  4. CENTOS7错误:Cannot find a valid baseurl for repo: base/7/x86_6
  5. 【数据库原理及应用】经典题库附答案(14章全)——第十三章:面向对象程数据库系统
  6. git gui怎么拉取项目代码_Git可视化极简易教程 — Git GUI使用方法
  7. 超值一篇分享,Docker:从入门到实战过程全记录
  8. 实训第二天的收获beep~~~
  9. sqlserver编号
  10. 你还在用notifyDataSetChanged?
  11. 数学趣事(自然数的因数)
  12. asp木马伪装成图片或其它,上传漏洞终极解决方法
  13. 计算机usb接口失灵,如何解决电脑USB接口失灵的问题
  14. 通达信日线day文件格式详解(含港股)
  15. 公租房摇号系统功能测试用例设计
  16. 部分win+R命令,mstsc远程桌面管理、RDP远程桌面协议
  17. devc++密室逃脱小游戏
  18. 河南科技大计算机专业录取分数线,河南科技大学历年各专业录取分数一览表
  19. 负数怎么求它的二进制呢?
  20. html里面包含xml,如何通过包含HTML的XML进行解析

热门文章

  1. git修改已提交commit的Author信息
  2. 为老年人熟悉智能手机的APP
  3. poi导出数据文件名错误_POI导出Excel报错“扩展名与文件的格式不匹配”
  4. 23、Numpy IO
  5. linux系统中pinctrl 和gpio子系统使用方法(教你点灯)
  6. exchange 2013 C盘空间不足清理
  7. teracopy php,Unraid 升级到 UnRaid 6.0的详细操作步骤(三)
  8. Vijos P1008 篝火晚会
  9. NDK学习笔记:FFmpeg解压MP34提取音频PCM(swrContext、swr_alloc_set_opts)
  10. 读书笔记---货币战争