ndnSIM学习(七)——转发处理forwarder.cpp、forwarder.hpp

前言

相比起之前的代码, forwarder.cpp 是一个大工程,头铁硬碰显然是不合适的。在看代码之前,我们应当对 Forwarder 类大致在干什么有一些初步的了解。实际上, Forwarder 类主要控制前向转发过程,我们可以把整个 Forwarder 类看作一个贴心的管家,通过 nameTree 管理手下的 fibpitcs ,还通过 m_faceTable 管理了手下的所有的 face 接口,并同时管理着其余的各项东西。

既然是是一个管家,那么涉及的内容肯定是盘根错节的。但幸运的是,我们能在官网上查到NFD的开发者手册,该手册截至目前最新的版本为第11版,下载链接为NFD Developer’s Guide (Latest Revision 11)

在本手册的第四章,对于转发部分有着详细的讲解。在本文的前半部分,我会引用一些手册里的图和内容,并结合我自己的理解对 Forward 过程进行梳理。在本文的后半部分,我会手动给整个 forwarder.cpp 代码标注注释,阐述我的理解,同时也方便读者对照前面的文字部分。

整体转发工作流程

引用手册里的图,整个转发流程其实就很清晰了。整个转发分为三个大板块: onIncomingInterestonIncomingDataonIncomingNack 。这里我对这些流程大方向进行一些简单的梳理:

  • 如果来了兴趣包,执行 onIncomingInterest
    • 如果重复的 Nonce ,则说明发生了循环,执行 onInterestLoop
    • 如果CS缓存未命中,则执行 onContentStoreMiss 函数,执行相应的策略
    • 如果CS缓存命中,则执行 onContentStoreHit 函数,执行相应的策略
    • 无论CS命中还是CS非命中,只要最后是决定要转发,都会执行 onOutgoingInterest
  • 如果来了数据包,执行 onIncomingData
    • 如果查PIT查不到请求,那么数据包是不请自来的,执行 onDataUnsolicited
    • 查有几个匹配的PIT条目,对所有条目的下游执行 onOutgoingData

在这里插入图片描述

部分名词说明

  • in-record和out-record我没有进行翻译。
  • downstream我翻译为了“下游”
  • upstream我翻译为了“上游”
  • 其中in-record代表interest的下游接口,是内容的请求者。out-record代表interest的上游接口,是潜在的内容源。interest从下游到上游,data从上游到下游。

onIncomingInterest工作流程

触发方式: Forwarder::onIncomingInterest 函数从 Forwarder::startProcessInterest 方法进入,该方法由接口 Face::afterReceiveData 信号触发。

引用手册里的图,转发流程如下图所示。值得注意的是,我用的代码版本是 ndnSIM2.8 (最新版本),具体流程和手册的图里有些区别,比如代码里没有检测Exceeds HopLimit的过程。对于整个流程,简要地说:

  • 首先以 /localhost 为前缀是本地通信保留的,所以以这个为前缀的将会被直接扔掉。
  • 然后检测当前随机数有没有在死亡随机数列表里,如果在,说明兴趣包循环了,执行 onInterestLoop
  • 再看有没有到生产者的区域,如果到了,就可以剥掉转发前缀了。
  • 接着查PIT(虽然我们在ndn原理里认为先查CS再查PIT,然而查CS开销可能会很大。所以可以先查PIT,如果PIT里有人请求过了,说明该兴趣如今还未决,CS肯定没有,就可以节省开销了),看看PIT里有没有重复随机数。如果没有就取消掉PIT的到期定时器,为PIT续1s
  • 最后查CS(其实先查CS再查PIT是等价的,只是先查PIT的话,如果PIT有就不用查CS了,节省开销),决定执行 onContentStoreHit 或者 onContentStoreMiss

在这里插入图片描述

onInterestLoop工作流程

上面 onIncomingInterest 说了,如果检测到相同的随机数,就说明发生了循环,这时就会执行 onInterestLoop 函数。这个时候分两种情况讨论:

  • 如果是点对点,那么发生循环相当于Not ACK,于是将原因标记为Nack。
  • 如果是多播的话,直接丢弃就行,不需要Nack。

onContentStoreHit工作流程

如果缓存命中,就设置好到期定时器,并执行相应的 afterContentStoreHit 策略,如下图所示:

在这里插入图片描述

onContentStoreMiss工作流程

如果缓存未命中,就给对应条目的PIT插入一个in-record,到期时间设置为最后一个in-record的时间。如果兴趣包内钦定好了下一跳的接口id,那就对该接口执行 onOutgoingInterest ,不然就执行策略 afterReceiveInterest ,通过FIB路由表查出下一跳(如果查到并决定转发,那么对查到的下一跳执行 onOutgoingInterest )。如下图所示:

在这里插入图片描述

onOutgoingInterest工作流程

我用的代码版本是 ndnSIM2.8 (目前最新版本),该版本代码里没有检测Exceeds HopLimit的检测过程,这里就按照我的版本代码来说吧

首先插入一个out-record(如果已经存在相同的接口就更新),记录下最后一个包的随机数和到期时间,然后执行 sendInterest 函数。如下图所示:
在这里插入图片描述

onInterestFinalize工作流程

一些善后工作,如插入死亡随机数列表、删除对应的PIT条目等。

onIncomingData工作流程

触发方式: Forwarder::onIncomingData 函数从 Forwarder::startProcessData 方法进入,该方法由接口 Face::afterReceiveData 信号触发。

简要地说:

  • 首先以 /localhost 为前缀是本地通信保留的,所以以这个为前缀的将会被直接扔掉。
  • 然后查PIT,如果PIT没有,但来了这个数据包,说明数据包是不请自来的(unsolicited)。如果PIT有,那么尝试将数据包插入CS。
  • 再检测有几个匹配的PIT条目(一般来说应该只有一个,因为多个PIT意味着多个转发策略,可能导致转发策略之间的冲突)
    • 如果单个匹配,将PIT到期时间设置为现在,并调用 afterReceiveData 策略,标记该PIT条目已被满足,删掉out-record,尝试插入死亡随机数列表。
    • 如果多个匹配,对于每个匹配项目,管道会先记住其pending的下一跳们(最后汇总一起执行 onOutgoingData ),其余操作与单个匹配类似,只不过变成了调用 beforeSatisfyInterest 策略,并清除条目的in-record。

在这里插入图片描述

onDataUnsolicited工作流程

如果数据是不请自来的,则根据 m_unsolicitedDataPolicy 的决定来判断。如果认为应当缓存就尝试缓存,否则啥都不干。

onOutgoingData工作流程

首先检查下 /localhost ,(然后下个版本计划进行流量管理,这个版本还没实现,)最后通过 sendData 函数传出去。

onIncomingNack函数

Nack(Not ACK)在NFD里会分为两部分处理: Incoming NackOutgoing Nack 。这部分讲的是Incoming Nack( processing of incoming Nacks)。

触发方式: Forwarder::onIncomingNack 函数从 Forwarder::startProcessNack 方法进入,该方法由接口 Face::afterReceiveNack 信号触发。

  • 首先,如果传入接口不是点对点的,那么直接丢弃(因为Nack只在点对点链路上被定义)。
  • 如果和out-record的最后一个记录的随机数相同,那么该record将会被标记为Nack。如果所有out-record都Nack了,将到期时间设置到期时间为0,并执行策略 afterReceiveNack
  • 如果随机数不同,那么就不执行操作。

onOutgoingNack函数

触发方式: Forwarder::onOutgoingNack 函数从 Strategy::sendNack 方法进入。

  • 首先,查in-record,如果查不到就不执行操作(都不知道哪个interest的问题,那还给鬼大爷发Nack啊)
  • 如果下一跳们不是点对点的,那么直接丢弃(因为Nack只在点对点链路上被定义)。
  • 如果没毛病的话,就给in-record的interest贴上Nack,通过传出接口让链路层执行 sendNack

具体代码分析环节

上面的内容基本上是我把官方文档结合我对代码的理解梳理了一遍,下面进入具体的代码分析环节。其实上面的内容已经足够我们理解整个 Forwarder 类了,但我们还是应当结合具体的代码,这样方便我们进行对照学习。

在进入代码分析之前,我想先向大家介绍一下信号(Signal)类。为什么要介绍它呢?因为它在整个 Forwarder 类、甚至整个ndnSIM代码里都是大量出现的。因此,我们单独开了一个小章节来讲解信号类。其实信号类很简单,也很好理解,说白了,一个信号里面储存了一堆操作(函数),每当这个信号被触发的时候,这些操作就会被执行一遍。或者对于基础好一点的同学来说:一个信号里面储存了函数指针链表,并且将运算符 () 重载为了执行函数指针链表内的所有函数

信号(Signal)类

所谓信号类,其实就是存了一堆函数(指针)。每当调用信号的时候,相当于依次执行这些函数。

想象一下,假设现在是夏天,你一回到宿舍就要打开空调、开灯、打开电脑、打开QQ、打开游戏(x);离开宿舍时要电脑、关灯、关空调。那么你的一天就可以写成 Today() 函数——

void Today(People myself, Controller control) {
    // 回宿舍(BeforeDoSomething)
    TurnOnAirConditioner(control);  // 开空调
    TurnOnLight(control);  // 开灯
    TurnOnComputer(control);  // 开电脑
    TurnOnGame(control);  // 开游戏
    // 做正事
    Dosomething(myself);
    // 离开宿舍前(AfterDoSomething)
    TurnOffComputer(control);  // 关电脑
    TurnOffLight(control);  // 关灯
    TurnOffAirConditioner(control);  // 关空调
}

但是这样写就太占篇幅了,代码可读性不强。实际上,我们完全可以召唤一个管家“小度小度”,定义 BeforeDoSomethingAfterDoSomething 两个信号,让小度小度代为管理这两个信号。(话说我是不是得收点广告费

// 我自己,我只管我自己的事情
// BeforeDoSomething、AfterDoSomething这些由管家小度小度(XiaoDuXiaoDu)来管理
class Myself
{
private:
    People myself;  // 自己
public:
    // 定义信号
    Signal<People, Controller > BeforeDoSomething;
    Signal<People, Controller > AfterDoSomething;
    // 我的一天
    void Today (Controller control) {
        BeforeDoSomething(control);
        Dosomething(myself);
        AfterDoSomething(control);
    }
};

// 我的管家小度小度(XiaoDuXiaoDu)
class XiaoDuXiaoDu
{
private:
    People* ZhuRen;  // 主人
public:
    // 连接信号
    void SetBeforeAfterDoSomething (Controller control) {
        ZhuRen->BeforeDoSomething.connect(TurnOnAirConditioner);
        ZhuRen->BeforeDoSomething.connect(TurnOnLight);
        ZhuRen->BeforeDoSomething.connect(TurnOnComputer);
        ZhuRen->BeforeDoSomething.connect(TurnOnGame);
        ZhuRen->AfterDoSomething.connect(TurnOffComputer);
        ZhuRen->AfterDoSomething.connect(TurnOffLight);
        ZhuRen->AfterDoSomething.connect(TurnOffAirConditioner);
    }
};

这样,我的 BeforeDoSomethingAfterDoSomething 都由贴心的管家小度小度用函数 SetBeforeAfterDoSomething 一键管理了,我的 Today 函数就会变得很简洁了。

具体而言,信号类的定义中最重要的部分如下所示。信号类重载了 () ,使得信号类的调用方式和函数一致。而且 Owner 作为了友元类,可以随意获取信号的私有成员。每当一个函数被 connect 到信号上,就会被添加到 m_slot 这个链表中。

template<typename Owner, typename ...TArgs>
class Signal : noncopyable
{
public:
  typedef function<void(const TArgs&...)> Handler;
  // 连接信号
  Connection connect(Handler handler);
  // 重载(),使得能用()直接传递函数参数,让信号看起来跟函数一样
  void operator()(const TArgs&... args);
  // 定义开头的模板参数Owner为友元类
  friend Owner;
  // 其余代码省略

private:
  SlotList m_slots;
  // 其余代码省略
};

信号类有什么好处呢?信号类可以让一个类调用另一个类的方法,比如 Today 这个函数就只用关心相对抽象层次高的内容,比如每天只需要干 BeforeDoSomethingDosomethingAfterDoSomething ,至于具体 BeforeDoSomething 是什么,可以由其他类来往里面添加(connect)事项。这样就可以用一个“管家”方便地管理下面的子类了

forwarder.hpp

从整个 forwarder.hpp 代码中的 Forwarder 类,结合前面的信号类,我们能够看到整个 forwarder.cpp 想做的事情。总的来说,就是 startProcessIntereststartProcessDatastartProcessNack 这三个函数衍生出来一串函数,并且单独为FIB搞了个 startProcessNewNextHop ,用于添加新的路由。

class Forwarder
{
public:
  // 构造函数,用于给成员的各种信号连接到本类的管道上
  explicit Forwarder(FaceTable& faceTable);
  VIRTUAL_WITH_TESTS ~Forwarder();

  // Get和Set方法
  const ForwarderCounters& getCounters() const
      { return m_counters; }
  fw::UnsolicitedDataPolicy& getUnsolicitedDataPolicy() const
      { return *m_unsolicitedDataPolicy; }
  void setUnsolicitedDataPolicy(unique_ptr<fw::UnsolicitedDataPolicy> policy)
      { BOOST_ASSERT(policy != nullptr);
        m_unsolicitedDataPolicy = std::move(policy); }

public: // 这些函数通过目标类的信号,使得目标类可以通过信号使用本类的管道
  void startProcessInterest(const FaceEndpoint& ingress, const Interest& interest)
      { this->onIncomingInterest(ingress, interest); }
  void startProcessData(const FaceEndpoint& ingress, const Data& data)
      { this->onIncomingData(ingress, data); }
  void startProcessNack(const FaceEndpoint& ingress, const lp::Nack& nack)
      { this->onIncomingNack(ingress, nack); }
  void startProcessNewNextHop(const Name& prefix, const fib::NextHop& nextHop)
      { this->onNewNextHop(prefix, nextHop); }

  // Get方法
  NameTree& getNameTree()
      { return m_nameTree; }
  Fib& getFib()
      { return m_fib; }
  Pit& getPit()
      { return m_pit; }
  Cs& getCs()
      { return m_cs; }
  Measurements& getMeasurements()
      { return m_measurements; }
  StrategyChoice& getStrategyChoice()
      { return m_strategyChoice; }
  DeadNonceList& getDeadNonceList()
      { return m_deadNonceList; }
  NetworkRegionTable& getNetworkRegionTable()
      { return m_networkRegionTable; }

public:  // 信号(Signal)
  signal::Signal<Forwarder, pit::Entry, Face, Data> beforeSatisfyInterest;
  signal::Signal<Forwarder, pit::Entry> beforeExpirePendingInterest;
  signal::Signal<Forwarder, Interest, Data> afterCsHit;
  signal::Signal<Forwarder, Interest> afterCsMiss;

PUBLIC_WITH_TESTS_ELSE_PRIVATE: // 管道(pipelines)
  VIRTUAL_WITH_TESTS void
  onIncomingInterest(const FaceEndpoint& ingress, const Interest& interest);
  VIRTUAL_WITH_TESTS void
  onInterestLoop(const FaceEndpoint& ingress, const Interest& interest);
  VIRTUAL_WITH_TESTS void
  onContentStoreMiss(const FaceEndpoint& ingress,
                     const shared_ptr<pit::Entry>& pitEntry, const Interest& interest);
  VIRTUAL_WITH_TESTS void
  onContentStoreHit(const FaceEndpoint& ingress, const shared_ptr<pit::Entry>& pitEntry,
                    const Interest& interest, const Data& data);
  VIRTUAL_WITH_TESTS void
  onOutgoingInterest(const shared_ptr<pit::Entry>& pitEntry,
                     const FaceEndpoint& egress, const Interest& interest);
  VIRTUAL_WITH_TESTS void
  onInterestFinalize(const shared_ptr<pit::Entry>& pitEntry);
  VIRTUAL_WITH_TESTS void
  onIncomingData(const FaceEndpoint& ingress, const Data& data);
  VIRTUAL_WITH_TESTS void
  onDataUnsolicited(const FaceEndpoint& ingress, const Data& data);
  VIRTUAL_WITH_TESTS void
  onOutgoingData(const Data& data, const FaceEndpoint& egress);
  VIRTUAL_WITH_TESTS void
  onIncomingNack(const FaceEndpoint& ingress, const lp::Nack& nack);
  VIRTUAL_WITH_TESTS void
  onOutgoingNack(const shared_ptr<pit::Entry>& pitEntry,
                 const FaceEndpoint& egress, const lp::NackHeader& nack);
  VIRTUAL_WITH_TESTS void
  onDroppedInterest(const FaceEndpoint& egress, const Interest& interest);
  VIRTUAL_WITH_TESTS void
  onNewNextHop(const Name& prefix, const fib::NextHop& nextHop);

PROTECTED_WITH_TESTS_ELSE_PRIVATE:
  void
  setExpiryTimer(const shared_ptr<pit::Entry>& pitEntry, time::milliseconds duration);
  VIRTUAL_WITH_TESTS void
  insertDeadNonceList(pit::Entry& pitEntry, Face* upstream);
#ifdef WITH_TESTS
  virtual void
  dispatchToStrategy(pit::Entry& pitEntry, std::function<void(fw::Strategy&)> trigger)
#else
  template<class Function>
  void
  dispatchToStrategy(pit::Entry& pitEntry, Function trigger)
#endif
  {
    trigger(m_strategyChoice.findEffectiveStrategy(pitEntry));
  }

private:
  ForwarderCounters m_counters;

  FaceTable& m_faceTable;
  unique_ptr<fw::UnsolicitedDataPolicy> m_unsolicitedDataPolicy;

  NameTree           m_nameTree;
  Fib                m_fib;
  Pit                m_pit;
  Cs                 m_cs;
  Measurements       m_measurements;
  StrategyChoice     m_strategyChoice;
  DeadNonceList      m_deadNonceList;
  NetworkRegionTable m_networkRegionTable;
  shared_ptr<Face>   m_csFace;

  // allow Strategy (base class) to enter pipelines
  friend class fw::Strategy;
};

Forwarder构造函数

整个构造函数主要是在连接各种信号,具体而言,干了这些事——

  • m_faceTable 添加保留一个接口( face ) contentstore:// (旧版本代码没有这一行)
  • m_faceTableafterAdd 信号装配操作(使得每次新添加的 face 的各信号能连接到对应的 Forwarder 的函数):
    • 让每个 face.afterReceiveInterest 连接到 this->startProcessInterest处理兴趣包
    • 让每个 face.afterReceiveData 连接到 this->startProcessData处理数据包
    • 让每个 face.afterReceiveNack 连接到 this->startProcessNack处理Nack
    • 让每个 face.onDroppedInterest 连接到 this->onDroppedInterest往FIB添加新路由时
  • m_faceTablebeforeRemove 信号装配操作: cleanupOnFaceRemoval (每次移除 face 前先 cleanup
  • m_fibafterNewNextHop 信号装配操作: this->startProcessNewNextHop
  • 设置 m_strategyChoice 为默认的 fw::BestRouteStrategy2
static NamegetDefaultStrategyName()
    { return fw::BestRouteStrategy2::getStrategyName(); }

Forwarder::Forwarder(FaceTable& faceTable)
  : m_faceTable(faceTable)
  , m_unsolicitedDataPolicy(make_unique<fw::DefaultUnsolicitedDataPolicy>())
  , m_fib(m_nameTree)
  , m_pit(m_nameTree)
  , m_measurements(m_nameTree)
  , m_strategyChoice(*this)
  , m_csFace(face::makeNullFace(FaceUri("contentstore://")))
{
  m_faceTable.addReserved(m_csFace, face::FACEID_CONTENT_STORE);

  m_faceTable.afterAdd.connect([this] (const Face& face) {
    face.afterReceiveInterest.connect(
      [this, &face] (const Interest& interest, const EndpointId& endpointId) {
        this->startProcessInterest(FaceEndpoint(face, endpointId), interest);
      });
    face.afterReceiveData.connect(
      [this, &face] (const Data& data, const EndpointId& endpointId) {
        this->startProcessData(FaceEndpoint(face, endpointId), data);
      });
    face.afterReceiveNack.connect(
      [this, &face] (const lp::Nack& nack, const EndpointId& endpointId) {
        this->startProcessNack(FaceEndpoint(face, endpointId), nack);
      });
    face.onDroppedInterest.connect(
      [this, &face] (const Interest& interest) {
        this->onDroppedInterest(FaceEndpoint(face, 0), interest);
      });
  });

  m_faceTable.beforeRemove.connect([this] (const Face& face) {
    cleanupOnFaceRemoval(m_nameTree, m_fib, m_pit, face);
  });

  m_fib.afterNewNextHop.connect([&] (const Name& prefix, const fib::NextHop& nextHop) {
    this->startProcessNewNextHop(prefix, nextHop);
  });

  m_strategyChoice.setDefaultStrategy(getDefaultStrategyName());
}

回顾下 forwarder.hpp 的函数的18~25行,构造函数里信号连接的 startProcessXXX 函数转到了 ForwarderonIncomingXXX 了。于是让我们再来关注这几个函数

  void startProcessInterest(const FaceEndpoint& ingress, const Interest& interest)
      { this->onIncomingInterest(ingress, interest); }
  void startProcessData(const FaceEndpoint& ingress, const Data& data)
      { this->onIncomingData(ingress, data); }
  void startProcessNack(const FaceEndpoint& ingress, const lp::Nack& nack)
      { this->onIncomingNack(ingress, nack); }
  void startProcessNewNextHop(const Name& prefix, const fib::NextHop& nextHop)
      { this->onNewNextHop(prefix, nextHop); }

前三个函数就是手册里讲的那一长串了

onIncomingInterest函数及其后续函数

onIncomingInterest函数

触发方式: Forwarder::onIncomingInterest 函数从 Forwarder::startProcessInterest 方法进入,该方法由接口 Face::afterReceiveData 信号触发。

每次传出 interest 时都会触发该函数,每个节点都会触发。

void
Forwarder::onIncomingInterest(const FaceEndpoint& ingress, const Interest& interest)
{
  // 给interest包打上IncomingFaceId标签
  NFD_LOG_DEBUG("onIncomingInterest in=" << ingress << " interest=" << interest.getName());
  interest.setTag(make_shared<lp::IncomingFaceIdTag>(ingress.face.getId()));
  ++m_counters.nInInterests;

  // 如果face不是local,同时interest的Name的前缀又是"localhost",则drop
  bool isViolatingLocalhost = ingress.face.getScope() == ndn::nfd::FACE_SCOPE_NON_LOCAL &&
                              scope_prefix::LOCALHOST.isPrefixOf(interest.getName());
  if (isViolatingLocalhost) {
    NFD_LOG_DEBUG("onIncomingInterest in=" << ingress
                  << " interest=" << interest.getName() << " violates /localhost");
    // (drop)
    return;
  }

  // 检查m_deadNonceList里有没有与当前包一样的内容,如果有,说明interest循环了,执行this->onInterestLoop
  bool hasDuplicateNonceInDnl = m_deadNonceList.has(interest.getName(), interest.getNonce());
  if (hasDuplicateNonceInDnl) {
    // goto Interest loop pipeline
    this->onInterestLoop(ingress, interest);
    return;
  }

  // 如果interest传到了Producer的地方,就剥掉forwardingHint
  if (!interest.getForwardingHint().empty() &&
      m_networkRegionTable.isInProducerRegion(interest.getForwardingHint())) {
    NFD_LOG_DEBUG("onIncomingInterest in=" << ingress
                  << " interest=" << interest.getName() << " reaching-producer-region");
    const_cast<Interest&>(interest).setForwardingHint({});
  }

  // 尝试将interest插入PIT表(如果在PIT找到了该interest,自然就不必插入了)
  shared_ptr<pit::Entry> pitEntry = m_pit.insert(interest).first;

  // 检测PIT里有没有相同的Nonce
  int dnw = fw::findDuplicateNonce(*pitEntry, interest.getNonce(), ingress.face);
  bool hasDuplicateNonceInPit = dnw != fw::DUPLICATE_NONCE_NONE;
  if (ingress.face.getLinkType() == ndn::nfd::LINK_TYPE_POINT_TO_POINT) {
    // p2p face的duplicate Nonce不被看作Loop
    hasDuplicateNonceInPit = hasDuplicateNonceInPit && !(dnw & fw::DUPLICATE_NONCE_IN_SAME);
  }
  // 如果重复了,说明interest循环了,执行this->onInterestLoop
  if (hasDuplicateNonceInPit) {
    // goto Interest loop pipeline
    this->onInterestLoop(ingress, interest);
    this->dispatchToStrategy(*pitEntry,
      [&] (fw::Strategy& strategy) { strategy.afterReceiveLoopedInterest(ingress, interest, *pitEntry); });
    return;
  }

  // 查CS
  if (!pitEntry->hasInRecords()) {
    m_cs.find(interest,
              bind(&Forwarder::onContentStoreHit, this, ingress, pitEntry, _1, _2),
              bind(&Forwarder::onContentStoreMiss, this, ingress, pitEntry, _1));
  }
  else {  // 如果都有人请求过了,那肯定说明CS里没有,就不用浪费时间查询了
    this->onContentStoreMiss(ingress, pitEntry, interest);
  }
}

onInterestLoop函数

onInterestLoop 函数就是执行一下 interest 包陷入循环后的善后操作

void
Forwarder::onInterestLoop(const FaceEndpoint& ingress, const Interest& interest)
{
  // 如果是广播或者ad-hoc就不特殊处理,直接return
  if (ingress.face.getLinkType() != ndn::nfd::LINK_TYPE_POINT_TO_POINT) {
    NFD_LOG_DEBUG("onInterestLoop in=" << ingress
                  << " interest=" << interest.getName() << " drop");
    return;
  }

  NFD_LOG_DEBUG("onInterestLoop in=" << ingress << " interest=" << interest.getName()
                << " send-Nack-duplicate");

  // 如果是p2p,interest都循环了,相当于Not ACK,执行Nack操作
  lp::Nack nack(interest);
  nack.setReason(lp::NackReason::DUPLICATE);
  ingress.face.sendNack(nack, ingress.endpoint);
}

onContentStoreMiss函数

如果CS中没有,执行 onContentStoreMiss 函数,给PIT加上输入记录,更新到期时间。

然后看看 interest 包里有没有钦定的下一跳路由的标签(如 ndn-simple.cpp 的例子就是没有),如果有,就让钦定好的下一跳路由进行 onOutgoingInterest 。如果找不到就根据 pitEntry 的状态决定是否转发(如果要转发,就是根据FIB的查出最佳的下一跳,然后执行对应的 onOutgoingInterest 函数)。

void
Forwarder::onContentStoreMiss(const FaceEndpoint& ingress,
                              const shared_ptr<pit::Entry>& pitEntry, const Interest& interest)
{
  NFD_LOG_DEBUG("onContentStoreMiss interest=" << interest.getName());
  ++m_counters.nCsMisses;
  afterCsMiss(interest);  // 似乎该信号是空的

  // 给pitEntry加上InRecord
  pitEntry->insertOrUpdateInRecord(ingress.face, interest);

  // 更新到期时间
  auto lastExpiring = std::max_element(pitEntry->in_begin(), pitEntry->in_end(),
                                       [] (const auto& a, const auto& b) {
                                         return a.getExpiry() < b.getExpiry();
                                       });
  auto lastExpiryFromNow = lastExpiring->getExpiry() - time::steady_clock::now();
  this->setExpiryTimer(pitEntry, time::duration_cast<time::milliseconds>(lastExpiryFromNow));

  // 如果钦定好了下一跳路由
  auto nextHopTag = interest.getTag<lp::NextHopFaceIdTag>();
  if (nextHopTag != nullptr) {
    // 并且能找到下一跳的face
    Face* nextHopFace = m_faceTable.get(*nextHopTag);
    if (nextHopFace != nullptr) {
      NFD_LOG_DEBUG("onContentStoreMiss interest=" << interest.getName()
                    << " nexthop-faceid=" << nextHopFace->getId());
      // 那么就交给下一跳路由进行Outgoing
      this->onOutgoingInterest(pitEntry, FaceEndpoint(*nextHopFace, 0), interest);
    }
    return;
  }

  // 根据pitEntry的状态信息,选择收到interest后的转发/不转发策略
  this->dispatchToStrategy(*pitEntry,
    [&] (fw::Strategy& strategy) {
      strategy.afterReceiveInterest(FaceEndpoint(ingress.face, 0), interest, pitEntry);
    });
}

onContentStoreHit函数

CS缓存命中将会触发,不过以 ndn-simple.cpp 为例,因为包里面一个Payload的 1024B 里是随机的垃圾数据,所以基本上不可能出现 CS 命中。

void
Forwarder::onContentStoreHit(const FaceEndpoint& ingress, const shared_ptr<pit::Entry>& pitEntry,
                             const Interest& interest, const Data& data)
{
  NFD_LOG_DEBUG("onContentStoreHit interest=" << interest.getName());
  ++m_counters.nCsHits;
  afterCsHit(interest, data);

  // 给数据打标签
  data.setTag(make_shared<lp::IncomingFaceIdTag>(face::FACEID_CONTENT_STORE));

  pitEntry->isSatisfied = true;  // 把该Entry标记为Satisfied
  pitEntry->dataFreshnessPeriod = data.getFreshnessPeriod();  //  更新dataFreshnessPeriod

  // 将到期时间设置为现在,到时候执行onInterestFinalize
  this->setExpiryTimer(pitEntry, 0_ms);

  beforeSatisfyInterest(*pitEntry, *m_csFace, data);  // 该信号由strategy、asf-strategy等设置
  this->dispatchToStrategy(*pitEntry,
    [&] (fw::Strategy& strategy) { strategy.beforeSatisfyInterest(pitEntry, FaceEndpoint(*m_csFace, 0), data); });

  // 执行Strategy::sendData->Forwarder::onOutgoingData
  this->dispatchToStrategy(*pitEntry,
    [&] (fw::Strategy& strategy) { strategy.afterContentStoreHit(pitEntry, ingress, data); });
}

onOutgoingInterest函数

如果决定转发 interest ,则会调用本函数

void
Forwarder::onOutgoingInterest(const shared_ptr<pit::Entry>& pitEntry,
                              const FaceEndpoint& egress, const Interest& interest)
{
  NFD_LOG_DEBUG("onOutgoingInterest out=" << egress << " interest=" << pitEntry->getName());

  // 给pitEntry加上OutRecord
  pitEntry->insertOrUpdateOutRecord(egress.face, interest);

  // 交给链路层执行doSendInterest
  egress.face.sendInterest(interest, egress.endpoint);
  ++m_counters.nOutInterests;
}

onInterestFinalize函数

一些善后工作

void
Forwarder::onInterestFinalize(const shared_ptr<pit::Entry>& pitEntry)
{
  NFD_LOG_DEBUG("onInterestFinalize interest=" << pitEntry->getName()
                << (pitEntry->isSatisfied ? " satisfied" : " unsatisfied"));

  if (!pitEntry->isSatisfied) {
    beforeExpirePendingInterest(*pitEntry);
  }

  // 如果MustBeFresh,且FreshnessPeriod小于LifeTime,则需要插入DeadNonceList
  this->insertDeadNonceList(*pitEntry, nullptr);

  // Increment satisfied/unsatisfied Interests counter
  if (pitEntry->isSatisfied) {
    ++m_counters.nSatisfiedInterests;
  }
  else {
    ++m_counters.nUnsatisfiedInterests;
  }

  // 删掉PIT表里对应条目 
  pitEntry->expiryTimer.cancel();
  m_pit.erase(pitEntry.get());
}

onIncomingData函数及其后续函数

onIncomingData函数

拉来了 data 包后,执行该函数,给每个in-record都发包。

void
Forwarder::onIncomingData(const FaceEndpoint& ingress, const Data& data)
{
  // 给data包打上IncomingFaceId标签
  NFD_LOG_DEBUG("onIncomingData in=" << ingress << " data=" << data.getName());
  data.setTag(make_shared<lp::IncomingFaceIdTag>(ingress.face.getId()));
  ++m_counters.nInData;

  // 如果face不是local,同时interest的Name的前缀又是"localhost",则drop
  bool isViolatingLocalhost = ingress.face.getScope() == ndn::nfd::FACE_SCOPE_NON_LOCAL &&
                              scope_prefix::LOCALHOST.isPrefixOf(data.getName());
  if (isViolatingLocalhost) {
    NFD_LOG_DEBUG("onIncomingData in=" << ingress << " data=" << data.getName() << " violates /localhost");
    // (drop)
    return;
  }

  // 如果PIT表里没有对应条目,说明data是不请自来的,执行onDataUnsolicited
  pit::DataMatchResult pitMatches = m_pit.findAllDataMatches(data);
  if (pitMatches.size() == 0) {
    // goto Data unsolicited pipeline
    this->onDataUnsolicited(ingress, data);
    return;
  }

  // 尝试往CS里插入data
  m_cs.insert(data);

  // 如果是单个PIT命中
  if (pitMatches.size() == 1) {
    auto& pitEntry = pitMatches.front();

    NFD_LOG_DEBUG("onIncomingData matching=" << pitEntry->getName());

    // 设置PIT到期,准备onOutgoingData
    this->setExpiryTimer(pitEntry, 0_ms);

    beforeSatisfyInterest(*pitEntry, ingress.face, data);
    // 调用afterReceiveData,这将执行Strategy::sendDataToAll(最终调用到onOutgoingData)
    this->dispatchToStrategy(*pitEntry,
      [&] (fw::Strategy& strategy) { strategy.afterReceiveData(pitEntry, ingress, data); });

    // 设置pitEntry为已满足(从而有可能添加到DeadNonceList)
    pitEntry->isSatisfied = true;
    pitEntry->dataFreshnessPeriod = data.getFreshnessPeriod();

    // 如果MustBeFresh,且FreshnessPeriod小于LifeTime,则需要插入DeadNonceList
    this->insertDeadNonceList(*pitEntry, &ingress.face);

    // 删掉该条目的out-record
    pitEntry->deleteOutRecord(ingress.face);
  }
  // 如果是多个PIT命中
  else {
    std::set<std::pair<Face*, EndpointId>> pendingDownstreams;
    auto now = time::steady_clock::now();

    // 把每个的in-record添加到“下一跳们”(后续集中处理)
    for (const auto& pitEntry : pitMatches) {
      NFD_LOG_DEBUG("onIncomingData matching=" << pitEntry->getName());

      // remember pending downstreams
      for (const pit::InRecord& inRecord : pitEntry->getInRecords()) {
        if (inRecord.getExpiry() > now) {
          pendingDownstreams.emplace(&inRecord.getFace(), 0);
        }
      }

      // 设置PIT到期,准备onOutgoingData
      this->setExpiryTimer(pitEntry, 0_ms);

      // invoke PIT satisfy callback
      beforeSatisfyInterest(*pitEntry, ingress.face, data);
      this->dispatchToStrategy(*pitEntry,
        [&] (fw::Strategy& strategy) { strategy.beforeSatisfyInterest(pitEntry, ingress, data); });

      // 设置pitEntry为已满足(从而有可能添加到DeadNonceList)
      pitEntry->isSatisfied = true;
      pitEntry->dataFreshnessPeriod = data.getFreshnessPeriod();

      // 如果MustBeFresh,且FreshnessPeriod小于LifeTime,则需要插入DeadNonceList
      this->insertDeadNonceList(*pitEntry, &ingress.face);

      // 清除掉in-record
      pitEntry->clearInRecords();
      pitEntry->deleteOutRecord(ingress.face);
    }

    // 对于“下一跳们”的每一个,都执行onOutgoingData
    for (const auto& pendingDownstream : pendingDownstreams) {
      if (pendingDownstream.first->getId() == ingress.face.getId() &&
          pendingDownstream.second == ingress.endpoint &&
          pendingDownstream.first->getLinkType() != ndn::nfd::LINK_TYPE_AD_HOC) {
        continue;
      }
      // goto outgoing Data pipeline
      this->onOutgoingData(data, FaceEndpoint(*pendingDownstream.first, pendingDownstream.second));
    }
  }
}

onDataUnsolicited函数

如果数据是不请自来的,执行本函数。看看对于不请自来的数据的策略是啥,如果策略是缓存,那么就尝试插入CS。否则啥都不干。

void
Forwarder::onDataUnsolicited(const FaceEndpoint& ingress, const Data& data)
{
  // 查m_unsolicitedDataPolicy,看看对于不请自来的数据的策略是啥
  fw::UnsolicitedDataDecision decision = m_unsolicitedDataPolicy->decide(ingress.face, data);
  if (decision == fw::UnsolicitedDataDecision::CACHE) {  // 如果策略是缓存
    // 尝试插入CS
    m_cs.insert(data, true);
  }

  NFD_LOG_DEBUG("onDataUnsolicited in=" << ingress << " data=" << data.getName() << " decision=" << decision);
}

onOutgoingData函数

做完一些检查工作后,准备发data包

void
Forwarder::onOutgoingData(const Data& data, const FaceEndpoint& egress)
{
  if (egress.face.getId() == face::INVALID_FACEID) {  // 接收数据方失效,return
    NFD_LOG_WARN("onOutgoingData out=(invalid) data=" << data.getName());
    return;
  }
  NFD_LOG_DEBUG("onOutgoingData out=" << egress << " data=" << data.getName());

  // 如果face不是local,同时interest的Name的前缀又是"localhost",则drop
  bool isViolatingLocalhost = egress.face.getScope() == ndn::nfd::FACE_SCOPE_NON_LOCAL &&
                              scope_prefix::LOCALHOST.isPrefixOf(data.getName());
  if (isViolatingLocalhost) {
    NFD_LOG_DEBUG("onOutgoingData out=" << egress << " data=" << data.getName() << " violates /localhost");
    // (drop)
    return;
  }

  // TODO: 下个版本计划进行流量管理,然而ndnSIM2.8这个版本还没实现

  // 发data包
  egress.face.sendData(data, egress.endpoint);
  ++m_counters.nOutData;
}

onIncomingNack函数

如果Incoming时Nack了,则执行该函数

void
Forwarder::onIncomingNack(const FaceEndpoint& ingress, const lp::Nack& nack)
{
  // 给nack包打上IncomingFaceId标签
  nack.setTag(make_shared<lp::IncomingFaceIdTag>(ingress.face.getId()));
  ++m_counters.nInNacks;

  // 如果不是点对点通信,那么根本都没定义Nack,直接扔掉
  if (ingress.face.getLinkType() != ndn::nfd::LINK_TYPE_POINT_TO_POINT) {
    NFD_LOG_DEBUG("onIncomingNack in=" << ingress
                  << " nack=" << nack.getInterest().getName() << "~" << nack.getReason()
                  << " link-type=" << ingress.face.getLinkType());
    return;
  }

  // 查PIT
  shared_ptr<pit::Entry> pitEntry = m_pit.find(nack.getInterest());
  // 如果PIT查不到就扔掉
  if (pitEntry == nullptr) {
    NFD_LOG_DEBUG("onIncomingNack in=" << ingress << " nack=" << nack.getInterest().getName()
                  << "~" << nack.getReason() << " no-PIT-entry");
    return;
  }

  // 查out-record
  auto outRecord = pitEntry->getOutRecord(ingress.face);
  // 如果out-record查不到,扔掉
  if (outRecord == pitEntry->out_end()) {
    NFD_LOG_DEBUG("onIncomingNack in=" << ingress << " nack=" << nack.getInterest().getName()
                  << "~" << nack.getReason() << " no-out-record");
    return;
  }

  // 和out-record的最后一个记录的随机数不同,扔掉
  if (nack.getInterest().getNonce() != outRecord->getLastNonce()) {
    NFD_LOG_DEBUG("onIncomingNack in=" << ingress << " nack=" << nack.getInterest().getName()
                  << "~" << nack.getReason() << " wrong-Nonce " << nack.getInterest().getNonce()
                  << "!=" << outRecord->getLastNonce());
    return;
  }

  NFD_LOG_DEBUG("onIncomingNack in=" << ingress << " nack=" << nack.getInterest().getName()
                << "~" << nack.getReason() << " OK");

  // 标记out-record为Nack
  outRecord->setIncomingNack(nack);

  // 如果所有out-record都Nack了,将到期时间设置为现在
  if (!fw::hasPendingOutRecords(*pitEntry)) {
    this->setExpiryTimer(pitEntry, 0_ms);
  }

  // 执行策略afterReceiveNack
  this->dispatchToStrategy(*pitEntry,
    [&] (fw::Strategy& strategy) { strategy.afterReceiveNack(ingress, nack, pitEntry); });
}

onOutgoingNack函数

如果Outgoing时Nack了,则执行该函数

void
Forwarder::onOutgoingNack(const shared_ptr<pit::Entry>& pitEntry,
                          const FaceEndpoint& egress, const lp::NackHeader& nack)
{
  if (egress.face.getId() == face::INVALID_FACEID) {
    NFD_LOG_WARN("onOutgoingNack out=(invalid)"
                 << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason());
    return;
  }

  // 检查in-record
  auto inRecord = pitEntry->getInRecord(egress.face);

  // 如果找不到in-record,就丢掉(都不知道哪个interest的问题,那还给鬼大爷发Nack啊)
  if (inRecord == pitEntry->in_end()) {
    NFD_LOG_DEBUG("onOutgoingNack out=" << egress
                  << " nack=" << pitEntry->getInterest().getName()
                  << "~" << nack.getReason() << " no-in-record");
    return;
  }

  // 如果不是点对点通信,那么根本都没定义Nack,直接扔掉
  if (egress.face.getLinkType() != ndn::nfd::LINK_TYPE_POINT_TO_POINT) {
    NFD_LOG_DEBUG("onOutgoingNack out=" << egress
                  << " nack=" << pitEntry->getInterest().getName() << "~" << nack.getReason()
                  << " link-type=" << egress.face.getLinkType());
    return;
  }

  NFD_LOG_DEBUG("onOutgoingNack out=" << egress
                << " nack=" << pitEntry->getInterest().getName()
                << "~" << nack.getReason() << " OK");

  // 准备好Nack包
  lp::Nack nackPkt(inRecord->getInterest());
  nackPkt.setHeader(nack);

  // 擦掉in-record
  pitEntry->deleteInRecord(egress.face);

  // 给interest发Nack
  egress.face.sendNack(nackPkt, egress.endpoint);
  ++m_counters.nOutNacks;
}

结语

剩下的就不写了,累死我了,也不重要,其实Nack那里都不是很重要了。。。

总的来说, Forwarder 类的功能很清晰,主体就是处理interest、data、Nack这三个玩意,其余具体的函数不过是它的实现罢了。比如处理interest,每次发包都要查PIT、CS,找下一跳查FIB。这也是 Forwarder 要管理NameTree的原因,因为转发功能就要用到这三个玩意,而它们通用的接口则是由FaceTable统一管理。具体的转发策略则是由 Strategy 及其派生类实现,具体的内部操作就要看CS、PIT、FIB里面的实现了,它们对于Forwarder而言只暴露出了接口,相当于透明的。

onIncomingInterestonIncomingDataonIncomingNack 函数则是由信号 Face::afterReceiveInterestFace::afterReceiveDataFace::afterReceiveNack 触发,这也降低了各模块的耦合程度。

Forwarder 类发散开, Strategy 是具体的转发策略, CsPitFib 是三个基本功能的实现, ProducerConsumer 是生产者和消费者, InterestData 是兴趣包和数据包的类型。这也呼应了ndnSIM学习(三)——ndnSIM源码阅读计划为什么要看这些内容了。

如果按照庖丁解牛来讲,整个 forwarder.cpp 就像是筋骨相接的缝隙。那么,下篇文章,让我们一起进一步深入学习ndnSIM吧。

  • 6
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值