前言
在上篇文章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
函数,该函数触发了 LinkService
的 afterReceiveInterest
信号。
等下,有没有感觉这个信号很熟悉?回一下 Forwarder
的构造函数,里面的 faceTable
将里面每个 face
的 afterReceiveInterest
信号都连接到了 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);
});
});
但好像还是差点意思,因为前面触发的是 LinkService
的 afterReceiveInterest
信号,这里是 face
的 afterReceiveInterest
信号。它们之间是不是有什么联系呢?我们看看 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)
又把自己的信号和链路服务的信号捆绑到了一起,所以 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
。终于,经过无数踢皮球后,我们终于看到了这一句话——
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包的全过程了。