一、前言
这里主要列出一些资源。
1. 查找API:
a. http://ndnsim.net/2.1/doxygen/annotated.html 首先在ndn中搜索,可以查看相关的描述和源码
b. https://www.nsnam.org/doxygen/index.html 如果在ndn中没有搜索到,移步至NS3中进行搜索
2. 如果有问题解决不了,可以发英文邮件至ndnsim@lists.cs.ucla.edu,会得到及时的解答。也可以查看其邮件记录http://www.lists.cs.ucla.edu/pipermail/ndnsim/,寻找类似的问题。
二、LCD原理简介
LCD即leave copy down,兴趣包每次被响应,都会被缓存在返回路径的下一跳,如果同一个内容被请求多次,那么它就会被缓存得离请求节点越来越近。
如图所示,C代表consumer
,P代表producer
,C第一次请求内容A,P响应请求,会将A缓存在节点3,第二次请求,节点3命中,直接返回内容,然后将内容缓存在节点2,以此类推。
三、实现思路
通过在返回的数据包中添加一个内容存储变量contentCopyTag
,用来判断节点是否存储内容来实现LCD。
内容存储变量contentCopyTag=1时转发节点并缓存内容,
contentCopyTag=0
时转发节点不缓存。数据包离开节点时,将contentCopyTag
减1。contentCopyTag
的初值为2,若将contentCopyTag
的初值为1,producer
发送数据后,内容存储变量contentCopyTag
变为0,当数据包到达第一个节点时因为contentCopyTag=0
则不会存储数据,这不符合LCD,故将contentCopyTag
的初值为2。假设转发节点上都为空,故兴趣包在到达
producer
之前无任何转发节点响应该兴趣包。当兴趣包到达producer
时,producer
响应兴趣包。为实现LCD,需要在返回的数据包中添加一个内容存储变量contentCopyTag
。producer
发送该数据包,此时contentCopyTag
由2变为1。当数据包到达第一个转发节点(即3号节点)时,节点判断
contentCopyTag
的值决定是否存储数据。contentCopyTag>0
(即=1)时存储数据。3号转发节点存储数据并发送该数据包,此时
contentCopyTag
由1变为0。之后该数据包经过的所有转发节点(不包括
consumer
节点)时,由于contentCopyTag=0
故不会将数据存储到节点内。当
consumer
再次发送该请求时,由于3号节点中存有该兴趣对应的数据,故3号节点响应该兴趣包,3号节点返回的数据包中添加一个内容存储变量contentCopyTag。以此类推最终所有转发节点都存有该数据。
四、具体实现
(一)contentCopyTag的实现
在ndnSIM中有两种Tag,一个是ns3::ndn::Tag
,这个Tag可以加入data
中(data继承自TagHost
),但是它只能在同一个节点内的不同components
之间传递,一旦离开这个节点,那么这个Tag将不复存在。
还有一个Tag是ns3::Tag
,这个Tag可以加入packet
中,它可以实现节点间的数据传递(至少在ndnSIM2.2之前)。
因此我们在这里实现一个继承自ns3::Tag
的Tag,来把contentCopyTag
交给下一跳。
实际上ndnSIM本身有一个类似的Tag,即FwHopCountTag
。我们就仿照它来实现我们的contentCopyTag。
在ns-3/src/ndnSIM/utils/
中,编写两个文件ndn-fw-content-copy-tag.hpp
和ndn-fw-content-copy-tag.cpp
代码如下:
//ndn-fw-content-copy-tag.hpp
#ifndef NDN_FW_CONTENT_COPY_TAG_H_
#define NDN_FW_CONTENT_COPY_TAG_H_
#include "ns3/tag.h"
namespace ns3 {
namespace ndn {
/**
* @brief Packet tag that is used to track hop count for Interest-Data pairs
*/
class FwcontentCopyTag : public Tag
{
public:
static TypeId
GetTypeId (void);
/**
* @brief Default constructor
* 数据包发送时将c_copyTag置为2,使第一个接收到数据包的节点可以缓存该数据包
*/
FwcontentCopyTag () : c_copyTag (2) { };
/**
* @brief Destructor
*/
~FwcontentCopyTag () { }
/**
* @brief Decrement tag
* 第一个接收到数据包的节点缓存数据后,调用Decrement(),目的是到达cunsumer之前的节点(除第一个节点)不缓存该数据
*/
void
Decrement () { c_copyTag --; }
/**
* @brief Get value of hop count
*/
int
Get () const { return c_copyTag; }
////////////////////////////////////////////////////////
// from ObjectBase
////////////////////////////////////////////////////////
virtual TypeId
GetInstanceTypeId () const;
////////////////////////////////////////////////////////
// from Tag
////////////////////////////////////////////////////////
/**/
virtual uint32_t
GetSerializedSize () const;
virtual void
Serialize (TagBuffer i) const;
virtual void
Deserialize (TagBuffer i);
virtual void
Print (std::ostream &os) const;
private:
//节点用来判断是否缓存内容的变量,true节点将数据缓存,false则不缓存数据
int c_copyTag;
};
} // namespace ndn
} // namespace ns3
#endif /* NDN_FW_CONTENT_COPY_TAG_H_ */
//ndn-fw-content-copy-tag.cpp
#include "ndn-fw-content-copy-tag.hpp"
namespace ns3 {
namespace ndn {
TypeId
FwcontentCopyTag::GetTypeId ()
{
static TypeId tid = TypeId("ns3::ndn::FwcountCopyTag")
.SetParent<Tag>()
.AddConstructor<FwcontentCopyTag>()
;
return tid;
}
TypeId
FwcontentCopyTag::GetInstanceTypeId () const
{
return FwcontentCopyTag::GetTypeId ();
}
/**/
uint32_t
FwcontentCopyTag::GetSerializedSize () const
{
return sizeof(uint32_t);
}
/**/
void
FwcontentCopyTag::Serialize (TagBuffer i) const
{
i.WriteU32 (c_copyTag);
}
void
FwcontentCopyTag::Deserialize (TagBuffer i)
{
c_copyTag = i.ReadU32 ();
}
void
FwcontentCopyTag::Print (std::ostream &os) const
{
os << c_copyTag;
}
} // namespace ndn
} // namespace ns3
(二)将Tag加入数据包中
以下双横线内的内容可跳过
实现我们的Tag之后就需要将其实例化并加入数据包中。在路径ns-3/src/ndnSIM/NFD/daemon/fw/
中,有一个forwarder.cpp
,我的理解是,这是一个中转站,所有的兴趣包、数据包都会经过这个中转站。我们需要在数据包发出去的时候将Tag加入包中。于是我们在forwarder.cpp
中找到onOutgoingData
函数,在对data
进行了一些处理之后调用了
outFace.sendData(data);
而outFace的类型是Face类型,说明还要在Face里面做文章。
在face.hpp中找到sendData函数(在路径ns-3/src/ndnSIM/NFD/daemon/face
中)
inline void
Face::sendData(const Data& data)
{
m_service->sendData(data);
}
在private列表里,找到m_service
unique_ptr<LinkService> m_service;
是一个指向LinkService
的指针,LinkService
在同目录下,
void
LinkService::sendData(const Data& data)
{
BOOST_ASSERT(m_transport != nullptr);
NFD_LOG_FACE_TRACE(__func__);
++this->nOutData;
doSendData(data);
afterSendData(data);
}
/** \brief performs LinkService specific operations to send a Data
*/
virtual void
doSendData(const Data& data) = 0;
doSendData是一个虚函数,需要用户实现。
这里ndnSIM已经有一个实现了,在路径ns3/src/ndnSIM/model/
下的ndn-net-device-link-service
中
在ns3/src/ndnSIM/model/
下的ndn-net-device-link-service.cpp
中重载了函数doSendData
void
NetDeviceLinkService::doSendData(const Data& data)
{
NS_LOG_FUNCTION(this << &data);
Ptr<Packet> packet = Convert::ToPacket(data);
send(packet);
}
可以看到它将data
转换为packet
来进行处理。
void
NetDeviceLinkService::send(Ptr<Packet> packet)
{
NS_ASSERT_MSG(packet->GetSize() <= m_netDevice->GetMtu(),
"Packet size " << packet->GetSize() << " exceeds device MTU "
<< m_netDevice->GetMtu());
FwHopCountTag tag;
packet->RemovePacketTag(tag);
tag.Increment();
packet->AddPacketTag(tag);
m_netDevice->Send(packet, m_netDevice->GetBroadcast(), L3Protocol::ETHERNET_FRAME_TYPE);
}
在send
函数中,可以看到它将一个FwHopCountTag
加入了packet
中。我们的contentCopyTag
也在这里加入。
void
NetDeviceLinkService::send(Ptr<Packet> packet)
{
NS_ASSERT_MSG(packet->GetSize() <= m_netDevice->GetMtu(),
"Packet size " << packet->GetSize() << " exceeds device MTU "
<< m_netDevice->GetMtu());
///////////////////////////////////////////
FwcontentCopyTag contentCopyTag;
packet->RemovePacketTag(contentCopyTag);
if(contentCopyTag.Get() > 0){
contentCopyTag.Decrement();
}
packet->AddPacketTag(contentCopyTag);
///////////////////////////////////////////
FwHopCountTag tag;
packet->RemovePacketTag(tag);
tag.Increment();
packet->AddPacketTag(tag);
m_netDevice->Send(packet, m_netDevice->GetBroadcast(), L3Protocol::ETHERNET_FRAME_TYPE);
}
首先声明一个FwcontentCopyTag
,这句话会调用其默认构造函数,将tag值初始化为2,下一句话:
packet->RemovePacketTag(contentCopyTag);
会将packet
中的contentCopyTag
移除,如果找到该tag,则返回true
,并移除它且将其值赋给contentCopyTag
,如果没有则返回false
不执行任何操作。
然后将tag值decrement一下,在add回去即可,这样就实现了tag的操作。无论是在producer
还是在其他结点都会得到正确的结果。
(三)判断Tag并决定是否缓存
继续回到forwarder.cpp
,找到onIncomingData
函数,其中有:
// CS insert
if (m_csFromNdnSim == nullptr)
m_cs.insert(*dataCopyWithoutPacket);
else
m_csFromNdnSim->Add(dataCopyWithoutPacket);
这段代码是将数据包插入CS中。我们在这里对tag进行判断,并决定是否缓存。
将其修改如下即可:
// CS insert
shared_ptr<Data> shared_data = make_shared<Data>(data);
//通过 ns3::ndn::Ns3PacketTag的getPacket函数拿到packet的指针
auto ns3PacketTag = shared_data->getTag<ns3::ndn::Ns3PacketTag>();
int contentCopy = 0;
ns3::ndn::FwcontentCopyTag contentCopyTag;
if(ns3PacketTag!=nullptr){
//判断contentCopyTag是否存在,也可以直接用RemovePacketTag函数
if(ns3PacketTag->getPacket()->PeekPacketTag(contentCopyTag)){
contentCopy = contentCopyTag.Get();
//如果大于零则缓存,否则不做任何事
if( contentCopy > 0){
if (m_csFromNdnSim == nullptr)
m_cs.insert(*dataCopyWithoutPacket);
else
m_csFromNdnSim->Add(dataCopyWithoutPacket);
}
}
}
//ns3PacketTag只在节点间传递,只有数据包离开节点时才会加入到数据包中,因此有可能为null
else{
std::cout<<"forwarder: onIncomingData: ns3PacketTag = null"<<std::endl;
}