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::SendPacketConsumerCbr是以恒定速率发送兴趣包请求的消费者。ConsumerZipfMandelbrot继承了ConsumerCbr,发包服从Zipf-Mandelbrot律,我也不知道是什么玩意。ConsumerWindow似乎是以滑动窗口的方式执行拥塞控制的消费者,我没有仔细看里面的代码,所以并不确定。代码里提到本类是高度实验性的,要小心使用ConsumerPcon继承了ConsumerWindow类,参考了论文https://dl.acm.org/citation.cfm?id=2984369