NDN代码分析(5) 兴趣包和数据包代码
从consumer发兴趣包到producer返回data包的全过程
一图明白整个发包、收包流程
- 黄颜色可以理解为预处理流程,发端执行「黄色+蓝色」的流程
- 中间的节点执行「绿色+蓝色」的流程
- 最后的节点(生产者或消费者)执行「绿色+蓝色+紫色」的流程
生产者发包
上篇文章中,我们知道了:整个ns3软件的仿真流程就是:添加事件->仿真器执行所有事件。
那么, Consumer
发兴趣包的事件是如何添加的呢?答案是:我们设置了消费者的兴趣频率是10,所以每秒发10个包。具体而言,消费者的类型是 ConsumerCbr
,而其中发包则是由函数 ScheduleNextPacket
控制,核心代码如下——
1 | void |
该函数的事件请求调用基类的 Consumer::SendPacket
函数,该函数造出一个兴趣包,然后用 m_appLink->onReceiveInterest
发出去.
1 | void |
AppLinkService::onReceiveInterest
则是调用了 LinkService::receiveInterest
函数,该函数触发了 LinkService
的 afterReceiveInterest
信号。
等下,有没有感觉这个信号很熟悉?回一下 Forwarder
的构造函数,里面的 faceTable
将里面每个 face
的 afterReceiveInterest
信号都连接到了 Forwarder::startProcessInterest
上。
1 | m_faceTable.afterAdd.connect([this] (const Face& face) { |
但好像还是差点意思,因为前面触发的是 LinkService
的 afterReceiveInterest
信号,这里是 face
的 afterReceiveInterest
信号。它们之间是不是有什么联系呢?我们看看 LinkService
的头文件定义,再结合 Face
的构造函数,发现里面有玄机。
1 | // 链路服务类 |
注意到 Signal
继承了 noncopyable
,而 Face
的构造函数中 afterReceiveData(service->afterReceiveData)
又把自己的信号和链路服务的信号捆绑到了一起,所以 LinkService
的 afterReceiveInterest
通过 Face
连接到了 Forwarder::startProcessInterest
上。
也就是说, LinkService
的 afterReceiveInterest
信号等价于 Forwarder::startProcessInterest
函数!
而 Forwarder::startProcessInterest
移交给了 Forwarder::onIncomingInterest
函数,详见文章ndnSIM学习(七)——转发处理forwarder.cpp、forwarder.hpp,该函数流程图如下
总之就是 Forwarder
对兴趣包进行处理。如果找到了,就会执行 Strategy::sendData
。否则就是 onOutgoingInterest
向下一跳继续往后找。这里我们假设没找到,继续向下一跳找。
这里执行 face.sendInterest
函数,又执行 LinkService::sendInterest
,然后 GenericLinkService::doSendInterest
到 sendNetPacket
给链路层进行 Frag
分片,分完片执行 sendLpPacket
,再通过 sendPacket
交给物理传输层执行 Transport::send
,再交给 NetDeviceTransport::doSend
,移交给 PointToPointNetDevice::Send
,其中执行了 TransmitStart
,又踢皮球给了 PointToPointChannel::TransmitStart
。终于,经过无数踢皮球后,我们终于看到了这一句话——
1 | Simulator::ScheduleWithContext (m_link[wire].m_dst->GetNode ()->GetId (), |
也就是说,如果向下一跳继续往后找的话,最终就是给目标节点 dst
添加一个 PointToPointNetDevice::Receive
事件。我个人比较好奇:每个包发了后,会让仿真系统推迟一段时间 txTime
,这是如何实现的。其实是在这里实现的——
1 | bool |
PointToPointNetDevice::Receive
事件触发后,会执行 m_promiscCallback
回调函数。妈呀,看到回调函数就头疼了。经过我一个小时的探案,终于发现这个回调函数绑定的是 Node::PromiscReceiveFromDevice
函数,这个函数踢皮球给 ReceiveFromDevice
,它执行该节点的 m_handlers
,而这个 ProtocolHandler
。。。我吐了,怎么又是回调函数啊!还是回调函数族。
还好,这个比较好找,这个回调函数是由 Node::RegisterProtocolHandler
执行 m_handlers.push_back
。这次运气很好,我眼睛很尖,一眼就看到了 NetDeviceTransport
的构造函数里有这样的代码
1 | RegisterProtocolHandler(MakeCallback(&NetDeviceTransport::receiveFromNetDevice, this), |
也就是说,Node::ReceiveFromDevice
把皮球踢给了 NetDeviceTransport::receiveFromNetDevice
,这个函数去掉报头扔给 Transport::receive
。这个函数终于把数据转交给了链路层函数 LinkService::receivePacket
,这个函数一脚踢给 GenericLinkService::doReceivePacket
。
1 | void |
也就是说,这个函数负责接收每个 Fragment
,合并它们,并检查其完整性。如果完整,那么就可以执行 decodeNetPacket
解码函数了。具体内容贴代码就行了,这里不细讲了。
1 | void |
因为我们这里收到的是兴趣包,所以执行 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包的全过程了。
apps之ndn-producer.cpp和ndn-consumer.cpp源码分析
~/ndnSIM/ns-3/src/ndnSIM/apps/ndn-producer.hpp
文件中定义了ndnSIM中生产者(producer)类的定义,代码如下:
1 | class Producer : public App { |
~/ndnSIM/ns-3/src/ndnSIM/apps/ndn-consumer.hpp
文件中定义了ndnSIM中消费者(consumer)类的定义,代码如下:
1 | class Consumer : public App { |
ndn-simple.cpp
里的消费者是 ConsumerCbr
,其中我推测 Cbr
的含义是 Constant BitRate
的缩写,所以 ConsumerCbr
其实就是以常数速率发包的消费者。
1 | class ConsumerCbr : public Consumer { |
其他消费者
因为基类 Consumer
的函数 ScheduleNextPacket
是纯虚函数,所以消费者一定得用其派生类,比如前面的 ConsumerCbr
就是一个派生类。这里列举一下ndnSIM的消费者(我没细看,可能有错误,仅供参考)
ConsumerBatches
用于批量管理消费者,下面管理了一个消费者的节点链表。一旦ConsumerBatches::StartApplication
被执行,整个类就会为它管理的所有节点执行ConsumerBatches::ScheduleNextPacket
。其中每过m_rtt->RetransmitTimeout()
就会触发该消费者的Consumer::SendPacket
ConsumerCbr
是以恒定速率发送兴趣包请求的消费者。ConsumerZipfMandelbrot
继承了ConsumerCbr
,发包服从Zipf-Mandelbrot律,我也不知道是什么玩意。ConsumerWindow
似乎是以滑动窗口的方式执行拥塞控制的消费者,我没有仔细看里面的代码,所以并不确定。代码里提到本类是高度实验性的,要小心使用ConsumerPcon
继承了ConsumerWindow
类,参考了论文https://dl.acm.org/citation.cfm?id=2984369