ndnSIM学习(九)——从consumer发兴趣包到producer返回data包的全过程

本文详细分析了NDN网络中消费者如何发送兴趣包以及生产者如何响应数据包的过程,涉及从消费者发包、中间节点处理、生产者回包的完整流程。讲解了从应用层到链路层、物理传输层的交互,包括信号传递、事件调度和数据包处理。整个流程展示了NDN网络内部的通信机制。
摘要由CSDN通过智能技术生成

前言

在上篇文章ndnSIM学习(八)——examples之ndn-simple.cpp每个函数逐行剖析中,我们分析了整个 ndn-simple.cpp 工作的底层原理。其中作为核心的,自然是整个网络的工作过程了:消费者是如何发送出兴趣包的?生产者是如何根据兴趣包返回数据包的?因为这部分内容与上篇文章相对独立,也比较冗长,所以我将这部分内容单独提出来作为了一篇文章。

一图明白整个发包、收包流程

  • 黄颜色可以理解为预处理流程,发端执行「黄色+蓝色」的流程
  • 中间的节点执行「绿色+蓝色」的流程
  • 最后的节点(生产者或消费者)执行「绿色+蓝色+紫色」的流程

注意!两种颜色中间衔接部分可能多过程或者少过程,实际以文字描述为准。虽然文字描述也不全面,建议以本文作为参考,实际以代码为准
在这里插入图片描述

生产者发包

上篇文章中,我们知道了:整个ns3软件的仿真流程就是:添加事件->仿真器执行所有事件。

那么, Consumer 发兴趣包的事件是如何添加的呢?答案是:我们设置了消费者的兴趣频率是10,所以每秒发10个包。具体而言,消费者的类型是 ConsumerCbr ,而其中发包则是由函数 ScheduleNextPacket 控制,核心代码如下——

void
ConsumerCbr::ScheduleNextPacket()
{
  if (m_firstTime) {
    m_sendEvent = Simulator::Schedule(Seconds(0.0), &Consumer::SendPacket, this);
    m_firstTime = false;
  }
  else if (!m_sendEvent.IsRunning())
    m_sendEvent = Simulator::Schedule((m_random == 0) ? Seconds(1.0 / m_frequency)
                                                      : Seconds(m_random->GetValue()),
                                      &Consumer::SendPacket, this);
}

该函数的事件请求调用基类的 Consumer::SendPacket 函数,该函数造出一个兴趣包,然后用 m_appLink->onReceiveInterest 发出去

void
Consumer::SendPacket()
{
  // ...省略前面的seq处理过程,下面是核心逻辑

  // shared_ptr<Interest> interest = make_shared<Interest> ();
  shared_ptr<Interest> interest = make_shared<Interest>();
  interest->setNonce(m_rand->GetValue(0, std::numeric_limits<uint32_t>::max()));
  interest->setName(*nameWithSequence);
  interest->setCanBePrefix(false);
  time::milliseconds interestLifeTime(m_interestLifeTime.GetMilliSeconds());
  interest->setInterestLifetime(interestLifeTime);

  WillSendOutInterest(seq);

  m_transmittedInterests(interest, this, m_face);
  m_appLink->onReceiveInterest(*interest);  // 核心逻辑

  ScheduleNextPacket();
}

AppLinkService::onReceiveInterest 则是调用了 LinkService::receiveInterest 函数,该函数触发了 LinkServiceafterReceiveInterest 信号。

等下,有没有感觉这个信号很熟悉?回一下 Forwarder 的构造函数,里面的 faceTable 将里面每个 faceafterReceiveInterest 信号都连接到了 Forwarder::startProcessInterest 上。

  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);
      });
  });

但好像还是差点意思,因为前面触发的是 LinkServiceafterReceiveInterest 信号,这里是 faceafterReceiveInterest 信号。它们之间是不是有什么联系呢?我们看看 LinkService 的头文件定义,再结合 Face 的构造函数,发现里面有玄机。

// 链路服务类
class LinkService : protected virtual LinkServiceCounters, noncopyable
{
// 前面的函数省略,关键在里面的成员函数
private:
  Face* m_face;  
  Transport* m_transport;
};

// ndnStack在InstallAll时,调用了本函数,为节点生成了链路、物理传输助手,并把它们绑定到face上
shared_ptr<Face>
StackHelper::PointToPointNetDeviceCallback(Ptr<Node> node, Ptr<L3Protocol> ndn,
                                           Ptr<NetDevice> device) const
{
  // 前面省略,这里创建的链路助手和物理传输层助手,并交给这个face管理
  auto linkService = make_unique<::nfd::face::GenericLinkService>(opts);

  auto transport = make_unique<NetDeviceTransport>(node, netDevice,
                                                   constructFaceUri(netDevice),
                                                   constructFaceUri(remoteNetDevice));

  auto face = std::make_shared<Face>(std::move(linkService), std::move(transport));
  face->setMetric(1);

  ndn->addFace(face);
  NS_LOG_LOGIC("Node " << node->GetId() << ": added Face as face #"
                       << face->getLocalUri());

  return face;
}

// Face的构造函数,构造Face时将会和链路服务、物理传输层服务捆绑到一起
Face::Face(unique_ptr<LinkService> service, unique_ptr<Transport> transport)
  : afterReceiveInterest(service->afterReceiveInterest)
  , afterReceiveData(service->afterReceiveData)
  , afterReceiveNack(service->afterReceiveNack)
  , onDroppedInterest(service->onDroppedInterest)
  , afterStateChange(transport->afterStateChange)
  , m_id(INVALID_FACEID)
  , m_service(std::move(service))
  , m_transport(std::move(transport))
  , m_counters(m_service->getCounters(), m_transport->getCounters())
  , m_metric(0)
{
  m_service->setFaceAndTransport(*this, *m_transport);
  m_transport->setFaceAndLinkService(*this, *m_service);
}

注意到 Signal 继承了 noncopyable ,而 Face 的构造函数中 afterReceiveData(service->afterReceiveData) 又把自己的信号和链路服务的信号捆绑到了一起,所以 LinkServiceafterReceiveInterest 通过 Face 连接到了 Forwarder::startProcessInterest 上。

也就是说, LinkServiceafterReceiveInterest 信号等价于 Forwarder::startProcessInterest 函数

Forwarder::startProcessInterest 移交给了 Forwarder::onIncomingInterest 函数,详见文章ndnSIM学习(七)——转发处理forwarder.cpp、forwarder.hpp,该函数流程图如下

在这里插入图片描述
总之就是 Forwarder 对兴趣包进行处理。如果找到了,就会执行 Strategy::sendData 。否则就是 onOutgoingInterest 向下一跳继续往后找。这里我们假设没找到,继续向下一跳找。

这里执行 face.sendInterest 函数,又执行 LinkService::sendInterest ,然后 GenericLinkService::doSendInterestsendNetPacket 给链路层进行 Frag 分片,分完片执行 sendLpPacket ,再通过 sendPacket 交给物理传输层执行 Transport::send ,再交给 NetDeviceTransport::doSend ,移交给 PointToPointNetDevice::Send ,其中执行了 TransmitStart ,又踢皮球给了 PointToPointChannel::TransmitStart终于,经过无数踢皮球后,我们终于看到了这一句话——

  Simulator::ScheduleWithContext (m_link[wire].m_dst->GetNode ()->GetId (),
                                  txTime + m_delay, &PointToPointNetDevice::Receive,
                                  m_link[wire].m_dst, p->Copy ());

太好了,我谢谢你全家啊!也就是说,如果向下一跳继续往后找的话,最终就是给目标节点 dst 添加一个 PointToPointNetDevice::Receive 事件。我个人比较好奇:每个包发了后,会让仿真系统推迟一段时间 txTime ,这是如何实现的。其实是在这里实现的——

bool
PointToPointNetDevice::TransmitStart (Ptr<Packet> p)
{
  // 前面的设置略

  Time txTime = m_bps.CalculateBytesTxTime (p->GetSize ());  // 就是这里,用m_bps结合p->GetSize()算出来的
  Time txCompleteTime = txTime + m_tInterframeGap;

  NS_LOG_LOGIC ("Schedule TransmitCompleteEvent in " << txCompleteTime.GetSeconds () << "sec");
  Simulator::Schedule (txCompleteTime, &PointToPointNetDevice::TransmitComplete, this);

  bool result = m_channel->TransmitStart (p, this, txTime);
  if (result == false)
    {
      m_phyTxDropTrace (p);
    }
  return result;
}

PointToPointNetDevice::Receive 事件触发后,会执行 m_promiscCallback 回调函数。妈呀,看到回调函数就头疼了。经过我一个小时的探案,终于发现这个回调函数绑定的是 Node::PromiscReceiveFromDevice 函数,这个函数踢皮球给 ReceiveFromDevice ,它执行该节点的 m_handlers ,而这个 ProtocolHandler 。。。我吐了,怎么又是回调函数啊!还是回调函数族。

还好,这个比较好找,这个回调函数是由 Node::RegisterProtocolHandler 执行 m_handlers.push_back 。这次运气很好,我眼睛很尖,一眼就看到了 NetDeviceTransport 的构造函数里有这样的代码

  m_node->RegisterProtocolHandler(MakeCallback(&NetDeviceTransport::receiveFromNetDevice, this),
                                  L3Protocol::ETHERNET_FRAME_TYPE, m_netDevice,
                                  true /*promiscuous mode*/);

也就是说,Node::ReceiveFromDevice 把皮球踢给了 NetDeviceTransport::receiveFromNetDevice ,这个函数去掉报头扔给 Transport::receive 。这个函数终于把数据转交给了链路层函数 LinkService::receivePacket ,这个函数一脚踢给 GenericLinkService::doReceivePacket 。等等,这里先婷婷(老梗了),我们瞄一眼代码

void
GenericLinkService::doReceivePacket(const Block& packet, const EndpointId& endpoint)
{
  try {
    // 省略前面的代码

    bool isReassembled = false;
    Block netPkt;
    lp::Packet firstPkt;
    std::tie(isReassembled, netPkt, firstPkt) = m_reassembler.receiveFragment(endpoint, pkt);
    if (isReassembled) {
      this->decodeNetPacket(netPkt, firstPkt, endpoint);
    }
  }
  catch (const tlv::Error& e) {
    // 省略这部分代码
  }
}

也就是说,这个函数负责接收每个 Fragment ,合并它们,并检查其完整性。如果完整,那么就可以执行 decodeNetPacket 解码函数了。具体内容贴代码就行了,这里不细讲了。

void
GenericLinkService::decodeNetPacket(const Block& netPkt, const lp::Packet& firstPkt,
                                    const EndpointId& endpointId)
{
  try {
    switch (netPkt.type()) {
      case tlv::Interest:
        if (firstPkt.has<lp::NackField>()) {
          this->decodeNack(netPkt, firstPkt, endpointId);
        }
        else {
          this->decodeInterest(netPkt, firstPkt, endpointId);
        }
        break;
      case tlv::Data:
        this->decodeData(netPkt, firstPkt, endpointId);
        break;
      default:
        ++this->nInNetInvalid;
        NFD_LOG_FACE_WARN("unrecognized network-layer packet TLV-TYPE " << netPkt.type() << ": DROP");
        return;
    }
  }
  catch (const tlv::Error& e) {
    ++this->nInNetInvalid;
    NFD_LOG_FACE_WARN("packet parse error (" << e.what() << "): DROP");
  }
}

因为我们这里收到的是兴趣包,所以执行 GenericLinkService::decodeInterest 。在这个函数将 Block 类型的 netPkt 输入强制转化为了 Interest 类型,然后按照 lp::Packet& firstPkt 给兴趣包打上各种标签,最后执行 LinkService::receiveInterest ,火箭发射,芜湖!

LinkService::receiveInterest 通过 afterReceiveInterest 信号,移交给 Forwarder 层的 Forwarder::startProcessInterest 再到 Forwarder::onIncomingInterest 。什么?你这里看不懂?前面已经分析过一遍了,不用再讲了吧,看不懂再回去看吧。

这里执行 face.sendInterest 函数,又执行 LinkService::sendInterest ,然后这里因为已经到了生产者了,而生产者由 AppLinkService 管理,所以不是 GenericLinkService::doSendInterest ,而是 AppLinkService::doSendInterest 进行。该函数添加了事件 App::OnInterest ,而实际上根据其虚特性,真正执行了 Producer::OnInterest

消费者回包

消费者用 Producer::OnInterest 收到了兴趣包后,返回一个数据包,调用 AppLinkService::onReceiveData ,再调用 LinkService::receiveData ,触发 afterReceiveData 。还是前面分析的,信号会连接到 Forwarder 层的 Forwarder::startProcessData 再到 Forwarder::onIncomingData ,详见文章ndnSIM学习(七)——转发处理forwarder.cpp、forwarder.hpp,该函数流程图如下
在这里插入图片描述

最后执行 onOutgoingData ,跳到 Face::sendData ,再跳到 LinkService::sendData ,继续跳到 GenericLinkService::doSendData ,跳到 GenericLinkService::sendNetPacket ,跳到 GenericLinkService::sendLpPacket ,跳到 LinkService::sendPacket ,终于该到物理传输层了。

交给物理传输层执行 Transport::send ,再交给 NetDeviceTransport::doSend ,移交给 PointToPointNetDevice::Send ,其中执行了 TransmitStart ,又踢皮球给了 PointToPointChannel::TransmitStart 。给目标节点 dst 添加一个 PointToPointNetDevice::Receive 事件,执行 m_promiscCallback 回调函数(这个回调函数绑定的是 Node::PromiscReceiveFromDevice 函数)。

这个函数踢皮球给 ReceiveFromDevice ,它执行该节点的 m_handlers ,通过回调函数执行 NetDeviceTransport::receiveFromNetDevice ,这个函数去掉报头扔给 Transport::receive 。这个函数终于把数据转交给了链路层函数 LinkService::receivePacket ,这个函数一脚踢给 GenericLinkService::doReceivePacket ,执行 decodeNetPacket 解码函数了。

好了!终于到和前面不一样的地方了!因为这里是数据包,执行 decodeData ,转到 LinkService::receiveData ,就到了 afterReceiveData 信号了。信号会连接到 Forwarder 层的 Forwarder::startProcessData 再到 Forwarder::onIncomingData

好了,转了一圈了,我就不转第二圈了,假设现在已经到了消费者的地方了。执行 onOutgoingData ,跳到 Face::sendData ,再跳到 LinkService::sendData因为已经到了消费者了,而消费者是由 AppLinkService 管理的,所以是由AppLinkService::doSendData 进行。该函数添加了事件 App::Data ,而实际上根据其虚特性,真正执行了 Consumer::Data

奈斯!全部搞定了

总结

虽然过程实在是很晕,但其实整个代码的逻辑还是很清晰的。将代码分为网络层、链路层、物理传输层,每层都干自己该干的事情,组合起来就是从consumer发兴趣包到producer返回data包的全过程了。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值