目录
一、安装
1、建议使用ubuntu20.04桌面版安装,因为桌面版安装出来还可以看到python生成的拓扑图。
2、安装教程请求参考官方网站的安装连接。
https://ndnsim.net/current/getting-started.html
3、下载源码的目录结构
4、判断是否安装成功,进入上图所示的ns-3目录下,运行以下命令
$ ./waf --run=ndn-simple --vis
出现如下所示的拓扑图:
点击图中的 "Simulate (F3)" 可以看到拓扑变绿色,数据正常传输,就表示安装成功了。
二、使用
2.1 ndnSIM模拟程序
ndnSIM的模拟程序的代码逻辑一般有3部分组成。
(1)画拓扑;
(2)配路由;
(3)在拓扑图中选两个节点,一个安装发包程序(consumer)用于发兴趣包,一个安装收包和发送数据包的程序(producer)。
2.2 ndnSIM模拟程序目录
如下图所示,ndnSIM的所有仿真模拟程序都放在 ns-3/scratch 目录下,每个程序必须以 .cc 结尾。
图中,作者自己创建了 wgh0,wgh1,wgh2,...等多个仿真程序,可以在ns-3目录下通过以下命令运行 wgh1.cc 这个仿真程序
$ ./waf --run=wgh1 --vis
读者可以直接复制ns-3/scratch 目录下 ndn-simple.cc 文件到相同目录下的 wgh1.cc 文件, 然后使用以上命令运行 wgh1.cc 这个仿真程序,这就相当与读者自己写了个仿真程序!!!
2.3 仿真程序分析
本节直接讲一种比较方便通用的仿真程序写法,不使用ndn-simple.cc为例,而是使用作者自己写的仿真代码为例。
#include "helper/ndn-app-helper.hpp"
#include "helper/ndn-fib-helper.hpp"
#include "helper/ndn-stack-helper.hpp"
#include "ns3/core-module.h"
#include "ns3/network-module.h"
#include "ns3/point-to-point-module.h"
#include "ns3/ndnSIM-module.h"
#include "utils/tracers/ndn-app-delay-tracer.hpp"
namespace ns3 {
int main(int argc, char* argv[])
{
CommandLine cmd;
cmd.Parse(argc, argv);
// 25 拓扑图的大小, 画拓扑图
AnnotatedTopologyReader topologyReader("", 25);
topologyReader.SetFileName("scratch/wgh1-tp.txt");
topologyReader.Read();
ndn::StackHelper ndnHelper ;
ndnHelper.InstallAll() ;
ndn::StrategyChoiceHelper::InstallAll("/", "/localhost/nfd/strategy/best-route");
ndn::FibHelper ndnFibHelper ;
Ptr<Node> node0 = Names::Find<Node>("Node0");
Ptr<Node> node1 = Names::Find<Node>("Node1");
Ptr<Node> node2 = Names::Find<Node>("Node2");
Ptr<Node> node3 = Names::Find<Node>("Node3");
Ptr<Node> node4 = Names::Find<Node>("Node4");
Ptr<Node> node5 = Names::Find<Node>("Node5");
Ptr<Node> node6 = Names::Find<Node>("Node6");
Ptr<Node> node7 = Names::Find<Node>("Node7");
Ptr<Node> node8 = Names::Find<Node>("Node8");
// 添加路由
std::string prefix = "/hello";
ndnFibHelper.AddRoute(node0, prefix, node1, 25) ;
ndnFibHelper.AddRoute(node1, prefix, node4, 25) ;
ndnFibHelper.AddRoute(node4, prefix, node5, 25) ;
ndnFibHelper.AddRoute(node5, prefix, node8, 25) ;
// 选两个点安装 consumer 和 producer
// Getting containers for the consumer/producer
Ptr<Node> producerNode = Names::Find<Node>("Node8");
Ptr<Node> consumerNode = Names::Find<Node>("Node0");
ndn::AppHelper consumerHelper("Wgh0App");
consumerHelper.Install(consumerNode);
ndn::AppHelper producerHelper("Wgh0Pro");
producerHelper.Install(producerNode);
// ---------------------------------------- install app
Simulator::Stop(Seconds(20.0));
Simulator::Run();
Simulator::Destroy();
return 0;
}
} // namespace ns3
int main(int argc, char* argv[])
{
return ns3::main(argc, argv);
}
2.3.1 画拓扑图
可以看到,画拓扑图的代码只有三行。
// 25 拓扑图的大小, 画拓扑图
AnnotatedTopologyReader topologyReader("", 25);
// 25 是拓扑图大小,拓扑图是画在一个正方形网格图上的,25是网络图面积,横坐标5个格,纵坐标5个格
topologyReader.SetFileName("scratch/wgh1-tp.txt");
// 读出当前目录下的画图文件“wgh1-tp.txt”
topologyReader.Read();
我们来看看"wgh1-tp.txt"的内容,文件前半段 route 区域是设置每个路由在网格图中的位置,link区域的配置路由器之间的连接。
router
# node comment yPos xPos
Node0 NA 3 1
Node1 NA 3 2
Node2 NA 3 3
Node3 NA 2 1
Node4 NA 2 2
Node5 NA 2 3
Node6 NA 1 1
Node7 NA 1 2
Node8 NA 1 3
link
# Each line should be in the following format (only first two are required, the rest can be omitted)
# srcNode dstNode bandwidth metric delay queue
# bandwidth: link bandwidth
# metric: routing metric
# delay: link delay
# queue: MaxPackets for transmission queue on the link (both directions)
Node0 Node1 1Mbps 1 10ms 10
Node0 Node3 1Mbps 1 10ms 10
Node1 Node2 1Mbps 1 10ms 10
Node1 Node4 1Mbps 1 10ms 10
Node2 Node5 1Mbps 1 10ms 10
Node3 Node4 1Mbps 1 10ms 10
Node3 Node6 1Mbps 1 10ms 10
Node4 Node5 1Mbps 1 10ms 10
Node4 Node7 1Mbps 1 10ms 10
Node5 Node8 1Mbps 1 10ms 10
Node6 Node7 1Mbps 1 10ms 10
Node7 Node8 1Mbps 1 10ms 10
我们看看画出来是什么效果。
2.3.2 配路由
ndn::FibHelper ndnFibHelper ;
Ptr<Node> node0 = Names::Find<Node>("Node0");
Ptr<Node> node1 = Names::Find<Node>("Node1");
Ptr<Node> node2 = Names::Find<Node>("Node2");
Ptr<Node> node3 = Names::Find<Node>("Node3");
Ptr<Node> node4 = Names::Find<Node>("Node4");
Ptr<Node> node5 = Names::Find<Node>("Node5");
Ptr<Node> node6 = Names::Find<Node>("Node6");
Ptr<Node> node7 = Names::Find<Node>("Node7");
Ptr<Node> node8 = Names::Find<Node>("Node8");
// 添加路由
std::string prefix = "/hello";
ndnFibHelper.AddRoute(node0, prefix, node1, 25) ;
// 给node0节点添加一条路由, "/hello" 下一跳指向 node1
ndnFibHelper.AddRoute(node1, prefix, node4, 25) ;
ndnFibHelper.AddRoute(node4, prefix, node5, 25) ;
ndnFibHelper.AddRoute(node5, prefix, node8, 25) ;
也可以使用全局路由算法给整个网络配置某条最佳路由,但作者觉得那样不太灵活,所以建议这样手工配。如果需要使用全局路由算法,读者可以参考代码https://ndnsim.net/current/examples.html。
2.3.3 安装程序
// 选两个点安装 consumer 和 producer
// Getting containers for the consumer/producer
Ptr<Node> producerNode = Names::Find<Node>("Node8");
Ptr<Node> consumerNode = Names::Find<Node>("Node0");
ndn::AppHelper consumerHelper("Wgh0App");
consumerHelper.Install(consumerNode);
ndn::AppHelper producerHelper("Wgh0Pro");
producerHelper.Install(producerNode);
// ---------------------------------------- install app
以上4行代码的意思就是在consumerNode 这个(Node0)节点安装 Wgh0App 这个程序, 在 producerNode这个节点(Node8)安装 Wgh0Pro 这个程序。
2.3.4 写自己的APP
Wgh0App和 Wgh0Pro ,是作者自己写的一个发兴趣包的程序和一个收兴趣包的应用程序。这两个程序放在“ns-3/src/ndnSIM/apps”目录下,如下图中的 wgh0-app.hpp, wgh0-app.cpp, wgh0-pro.hpp, wgh0-pro.cpp这4个文件。
读者可以直接将这4个文件复制到“ns-3/src/ndnSIM/apps”目录下,就可以直接在模拟仿真程序里用了。这四个程序的代码,首先是发包程序的.hpp和.cpp代码
// wgh0-app.hpp
#ifndef WGH0_APP_H_
#define WGH0_APP_H_
#include "ns3/ndnSIM/apps/ndn-app.hpp"
namespace ns3 {
class Wgh0App : public ndn::App {
public:
static TypeId
GetTypeId();
virtual void
StartApplication();
virtual void
StopApplication();
virtual void
OnData(std::shared_ptr<const ndn::Data> contentObject);
private:
void SendInterest();
static void sendThread(Wgh0App *_this) ;
};
} // namespace ns3
#endif // CUSTOM_APP_H_
读者不用理会.hpp文件,应该什么都不用改。都用以上的模板就行了,可以改类名,改了类名就相当于改了APP名称,在仿真程序中安装时,传入的就是类名。
// wgh0-app.cpp
#include <iostream>
#include <thread>
#include "model/ndn-common.hpp"
#include "wgh0-app.hpp"
#include "ns3/ptr.h"
#include "ns3/log.h"
#include "ns3/simulator.h"
#include "ns3/packet.h"
#include "ns3/ndnSIM/helper/ndn-stack-helper.hpp"
#include "ns3/ndnSIM/helper/ndn-fib-helper.hpp"
#include "ns3/random-variable-stream.h"
#include "ns3/ndnSIM/ndn-cxx/lp/MultiIdentifierHeader.hpp"
#include "ns3/ndnSIM/ndn-cxx/lp/tags.hpp"
NS_LOG_COMPONENT_DEFINE("Wgh0App");
namespace ns3{
NS_OBJECT_ENSURE_REGISTERED(Wgh0App);
// 根据类名对下面的代码做小修改
TypeId Wgh0App::GetTypeId(){
static TypeId tid = TypeId("Wgh0App").SetParent<ndn::App>().AddConstructor<Wgh0App>();
return tid;
}
// 作者自己写了个隔50ms发一个兴趣包的线程
void Wgh0App::sendThread(Wgh0App *_this){
while (true) {
Simulator::Schedule(Seconds(1.0), &Wgh0App::SendInterest, _this);
usleep(50000) ;
}
}
// 这个函数会被ns-3调用一次来启动我们写的代码,因此我们在这个函数里启动发包线程
void Wgh0App::StartApplication(){
ndn::App::StartApplication();
//Simulator::Schedule(Seconds(1.0), &Wgh0App::SendInterest, this);
std::thread t(sendThread, this);
t.detach();
}
// 不用改这个函数
void Wgh0App::StopApplication(){
ndn::App::StopApplication();
}
// 发兴趣包的函数,直接在下面的基础上改改兴趣包的名称应该就满足好多需求了。
// 读者可以自己构造兴趣包,并调用
// m_transmittedInterests(interestPtr, this, m_face);和 m_appLink->onReceiveInterest(*interestPtr); 就能把兴趣包发出去了
void Wgh0App::SendInterest(){
static uint64_t interestId = 0 ;
//while (true) {
ndn::Name interestName("/hello");
interestName.appendSequenceNumber(interestId ++) ;
auto interestPtr = std::make_shared<ndn::Interest>(interestName);
Ptr<UniformRandomVariable> rand = CreateObject<UniformRandomVariable>();
interestPtr->setNonce(rand->GetValue(0, std::numeric_limits<uint32_t>::max()));
interestPtr->setInterestLifetime(ndn::time::seconds(2));
std::cout << "send interest : " << *interestPtr << std::endl;
m_transmittedInterests(interestPtr, this, m_face);
m_appLink->onReceiveInterest(*interestPtr);
//sleep(1) ;
//}
}
// 收到数据包的处理函数,读者可以按自己的需要修改
void Wgh0App::OnData(std::shared_ptr<const ndn::Data> data){
std::cout << "DATA received for name " << data->getName() << std::endl;
}
}
接下来是收兴趣包并发出数据包的代码,以下2个.hpp和.cpp文件
// wgh0-pro.hpp
#ifndef WGH0PRO_H_
#define WGH0PRO_H_
#include "ns3/ndnSIM/apps/ndn-app.hpp"
namespace ns3 {
class Wgh0Pro : public ndn::App {
public:
static TypeId
GetTypeId();
Wgh0Pro();
// Receive all Interests but do nothing in response
void
OnInterest(std::shared_ptr<const ndn::Interest> interest);
protected:
// inherited from Application base class.
virtual void
StartApplication();
virtual void
StopApplication();
};
} // namespace ns3
#endif
同样不会对.hpp做什么修改。
#include "wgh0-pro.hpp"
#include "model/ndn-common.hpp"
#include "ns3/log.h"
#include "ns3/ndnSIM/helper/ndn-fib-helper.hpp"
#include <memory>
#include "ns3/ndnSIM/ndn-cxx/lp/MultiIdentifierHeader.hpp"
#include "ns3/ndnSIM/ndn-cxx/lp/tags.hpp"
NS_LOG_COMPONENT_DEFINE("Wgh0Pro");
namespace ns3 {
// Necessary if you are planning to use ndn::AppHelper
NS_OBJECT_ENSURE_REGISTERED(Wgh0Pro);
TypeId
Wgh0Pro::GetTypeId()
{
static TypeId tid = TypeId("Wgh0Pro").SetParent<ndn::App>().AddConstructor<Wgh0Pro>();
return tid;
}
Wgh0Pro::Wgh0Pro()
{
}
// 收到兴趣包的响应函数,读者可以按自己的需要返回数据包,并调用
// m_transmittedDatas(data, this, m_face); 和 m_appLink->onReceiveData(*data);
// 把数据包发出去就行了
void Wgh0Pro::OnInterest(std::shared_ptr<const ndn::Interest> interest)
{
ndn::App::OnInterest(interest); // forward call to perform app-level tracing
// do nothing else (hijack interest)
//
std::cout << "Receive interest : " << *interest << std::endl;
std::string content = "hello" ;
std::shared_ptr<ndn::Data> data = std::make_shared<ndn::Data>();
data->setName(interest->getName());
data->setContent((const uint8_t*)content.data(), content.size()) ;
ndn::KeyChain keyChain ;
keyChain.sign(*data);
m_transmittedDatas(data, this, m_face);
m_appLink->onReceiveData(*data);
NS_LOG_DEBUG("Do nothing for incoming interest for" << interest->getName());
}
void
Wgh0Pro::StartApplication()
{
App::StartApplication();
// equivalent to setting interest filter for "/prefix" prefix
ndn::FibHelper::AddRoute(GetNode(), "/hello", m_face, 0);
}
void
Wgh0Pro::StopApplication()
{
App::StopApplication();
}
} // namespace ns3
三、进阶
如果读者读需要改NDN的源代码,直接改 ns-3/src/ndnSIM 目录下的 ndn-cxx代码和 NFD代码即可。改完后直接使用 ./waf --run=<你的仿真程序名> --vis 这个命令来运行仿真程序就可以了。