ndnSIM这套东西,我在实验室摸爬滚打了一个多月,踩了无数坑之后,总算跑通了第一个完整的仿真实验。回头看看,很多弯路其实完全没必要走。这篇笔记我把我从零开始的学习路径、环境搭建、核心概念梳理、第一个仿真脚本的完整实现,以及我实际运行中遇到的问题和排查思路,原原本本整理出来。如果你也正准备入门NDN(命名数据网络)仿真,这篇应该能帮你省下至少两周的摸索时间。
坦白说,ndnSIM的学习曲线不算平缓。NS-3本身就是一座大山,NDN又是在传统IP协议栈之外另起炉灶的一套全新网络架构,两边叠加在一起,初学者很容易懵圈。但反过来讲,只要搞清楚了ndnSIM的基本套路,后面做实验、改代码、看结果,其实都是水磨工夫,并没有想象中那么玄乎。
这篇笔记是整个系列的第一篇,重点解决“从零到能跑通”的问题。我会告诉你为什么研究NDN需要ndnSIM、环境怎么搭最省心、ndnSIM的代码结构是怎么组织的,然后带你手写一个最简单的NDN通信仿真,最后把我踩过的坑和排查方法系统地列出来。内容会贴合我用Ubuntu 22.04 + NS-3.35 + ndnSIM 2.8的实践环境,你跟着一步步做,基本能复现同样的结果。
1. 内容整体设计与思路拆解
1.1 为什么研究NDN网络需要ndnSIM
NDN(Named Data Networking)是一种以内容为中心的未来网络架构。它和传统IP网络最大的区别在于:IP网络是“主机到主机”的通信模型,你要访问一个资源,必须先知道它在哪台服务器上;NDN是“内容名到内容”的通信模型,你只要说“我要什么”,网络自己负责找数据在哪、怎么传回来,中间经过的每个路由器都可以缓存经过的内容。
这就带来了一个现实问题:NDN这种架构,你怎么验证它的性能?用真机部署成本太高,而且NDN协议栈的成熟实现并不多。这时候,仿真平台就是最现实的选择。ndnSIM正是跑在NS-3上的NDN模拟器。NS-3本身就是学术界用得最广泛的离散事件网络仿真器,ndnSIM把NDN的转发流程、缓存管理、策略路由等机制以模块化的方式集成进去,让研究者可以灵活地模拟各种真实网络场景。
我选择ndnSIM而不是自己从头写模拟器的原因很直接:网络仿真涉及的细节太庞杂了。光是底层的网络接口、信道延迟、队列管理这些机制,自己重造一遍轮子就需要大量时间和精力,而且很容易引入逻辑漏洞。ndnSIM把这一层全部交给NS-3处理,我只需要关注NDN转发层的逻辑和应用层场景,这大大降低了实验的工作量。
1.2 学习笔记系列的组织逻辑
这一系列笔记,我是按照“能跑通、能看懂、能改代码、能出成果”四个层次来组织的。
第一个层次是环境准备和基础概念,也就是这一篇的主要内容。第二个层次会深入ndnSIM的核心类和回调机制,讲清楚一个Interest包从发送端发出之后,整个协议栈内部是如何流转的。第三个层次会讲怎么自定义转发策略、修改缓存替换算法,以及如何在ndnSIM里接入真实网络拓扑。第四个层次涉及实验结果的数据采集和绘图分析,比如用Python处理Trace文件,直观呈现缓存命中率和网络延迟的差异。
这样的设计逻辑是:每一步都建立在前一步可运行的代码基础上,避免一上来就陷入源码阅读的泥潭。说实话,我见过很多初学者一上来就把ndnSIM的所有源码翻个遍,结果半个月过去还在看代码,脚本一个没跑通,这其实是很可惜的。先让代码动起来,再倒回去理解原理,对大多数人来说是性价比最高的路径。
2. 环境准备与安装避坑指南
2.1 操作系统与NS-3版本选择
我推荐直接在Ubuntu 22.04 LTS上操作,如果是Windows用户,建议装VMware或者VirtualBox跑虚拟机,分配4核CPU和8G内存以上会比较舒服。有条件的同学直接用WSL2也可以,但注意WSL2的图形界面支持稍弱,如果你后面要用netanim动画工具,WSL2可能折腾一些。
版本搭配是重头戏。ndnSIM 2.8官方支持的是NS-3.35,ndnSIM 2.7对应NS-3.33,版本不能随便混搭。我第一次安装的时候图省事,直接用apt装了一个系统自带的NS-3.36,然后强行编译ndnSIM 2.8,结果编译报错各种莫名其妙,最后总结起来就是一句话:版本要严格对表,不要想当然。
这里我给出当前经过验证的版本组合:
- Ubuntu 22.04 LTS
- NS-3.35
- ndnSIM 2.8
- gcc 11.x
- Python 3.10
如果你机器上装了多个gcc版本,编译前务必检查一下当前默认gcc版本,推荐用gcc-11。gcc版本过高(比如13.x)编译NS-3.35有时会遇到一些奇怪的模板报错,这些问题排查起来非常消耗精力。
2.2 依赖安装与编译过程
打开终端,先把系统依赖装齐。以下命令在Ubuntu 22.04上实测可直接运行:
sudo apt update sudo apt install -y build-essential libsqlite3-dev libboost-all-dev \ libssl-dev git python3 python3-dev python3-pip \ doxygen graphviz imagemagick texlive-font-utils \ python3-pip openmpi-bin openmpi-common openmpi-doc libopenmpi-dev这些依赖里,build-essential是编译工具链,libboost-all-dev是NS-3的核心依赖库(NS-3很多基础数据结构都基于boost),sqlite3和ssl是打开NS-3某些可选功能需要的。如果你后面要使用可视化工具,doxygen和graphviz也要装上。建议不要跳过这些直接往后走,我第一次图省事跳过了一些看起来无关紧要的包,结果在编译某个模块的时候报了缺头文件的错误,来回装了三次才补齐。
接下来克隆ndnSIM源码。官方推荐的安装方式是直接把ndnSIM的核心模块放到NS-3的src目录下,但更省事的方式是用ndnSIM官方维护的脚本。我个人是用手动方式克隆的,这样对代码结构更清楚:
cd ~ git clone https://github.com/named-data-ndnSIM/ns-3-dev.git ns-3 git clone https://github.com/named-data-ndnSIM/ndnSIM.git ns-3/src/ndnSIM cd ns-3 git checkout ns-3.35 cd src/ndnSIM git checkout ndnSIM-2.8这里有个细节:ndnSIM官方仓库推荐的使用方式是把ndnSIM作为NS-3的扩展模块放进src目录,其根本原因是ndnSIM的代码深度依赖NS-3的底层模拟机制,例如Packet、Channel、Node、Application等类都需要直接编译在一起。如果分开编译成外部库,接口管理的复杂度会高很多。
然后回到ns-3根目录,进入编译流程:
cd ~/ns-3 ./waf configure --enable-tests --enable-examples配置完成后,确认输出里出现了类似“ndnSIM”模块的信息,然后再开始编译:
./waf第一次编译时间会比较长,我用了大约40分钟(8核编译),如果机器配置低可能要一个多小时。编译过程中如果报错,最常见的两类:一类是头文件找不到,一般是因为boost版本不匹配;另一类是C++标准库的问题,一般调整gcc版本就能解决。
编译结束后,可以用ndnSIM官方自带的示例做验证:
./waf --run ndn-simple如果输出里能看到一系列日志信息,最后没有segmentation fault(段错误),就说明环境基本可用了。
2.3 目录结构速览
编译成功之后,建议先花10分钟把ndnSIM的目录结构过一遍:
ns-3/src/ndnSIM/ ├── apps/ # 预置的NDN应用层模块,比如消费者、生产者 ├── core/ # NDN核心数据结构,如Interest、Data、Name ├── faces/ # 网络接口抽象,负责包的收发 ├── network/ # 转发相关逻辑 ├── utils/ # 工具类,例如数据集追踪 ├── helper/ # 用于在仿真脚本中快速安装NDN协议栈 ├── examples/ # 官方案例,学习的首要材料 └── tests/ # 单元测试理解这几个目录的用途之后,你阅读代码时就会有一个大致的方向感。比如你想知道一个兴趣包的转发策略是怎么写的,去network目录找;你想知道怎么在仿真里让应用周期性地发请求,去apps目录看consumer应用的实现。
3. 核心概念与ndnSIM的架构映射
3.1 NDN的网络层语义
NDN网络中有两种核心包类型:Interest(兴趣包)和Data(数据包)。通信的发起者是请求方,它发送一个携带内容名字的Interest包;网络中的某个节点如果持有对应的内容(可能是原始生产者,也可能是缓存了这份数据的中间路由器),就会回复一个Data包,Data包会沿着Interest包经过的路径原路返回。
这就像你在一座图书馆里想要一本书,你不需要知道书在哪层哪架,只需要把书名告诉图书管理员。管理员自己会查,如果架上没有,他会问别的图书馆,拿到后交给你。整个过程中,你要的只是内容(书名对应的书),而不是某个固定的服务器地址。
ndnSIM在NS-3中把这种语义具体化为:Interest包和Data包都是NS-3的Packet协议头封装后的特殊数据结构,每个NDN节点内部维护三张核心表:
- CS(Content Store,内容存储):相当于节点本地的缓存,按内容名索引。
- PIT(Pending Interest Table,待定兴趣表):记录我曾经转发过但还没收到回应的Interest包,它可以实现数据回传路径的追踪以及请求聚合。
- FIB(Forwarding Information Base,转发信息表):记录一条内容名的前缀应该从哪个Face(网络接口)转发出去,功能和IP网络中的路由表类似。
3.2 一次完整的请求流程
我们在仿真中设置一个消费者节点和一个生产者节点。消费者发出一个名字为/example/data的Interest包,这个过程在ndnSIM中会触发以下流程:
Interest包进入消费者节点的NDN协议栈,查询CS,发现没有对应内容;查询PIT,也没有相同条目(否则会合并请求);查询FIB,找到应该发往生产者节点的那条Face,然后转发出去。
中间路由器收到Interest包之后,同样先查询CS,如果有缓存就直接返回Data;如果没有,则查询FIB,继续向下游转发。同时,路由器会在PIT中记录这个Interest包是从哪个Face进来的,以便Data包回来时知道往哪走。
生产者收到Interest包后,直接在CS中构造或查找Data包,沿着PIT记录的路径回传。每一个中间节点收到Data包时,都会在PIT中找到匹配的条目,记录该Data包应该向哪个Face转发,同时把Data包加入自己的CS,以服务后续相同的请求。
这个过程和IP网络有本质区别:IP网络转发的是“给谁”的问题,NDN转发的是“要什么”的问题。这也带来了一个天然优势:缓存可以发生在网络内部的任何节点,而不需要应用层单独设计CDN或缓存服务器。
3.3 策略、缓存与应用层的关系
ndnSIM把转发决策抽象为“转发策略”(ForwardingStrategy)。默认策略是BestRouteStrategy,它会根据FIB中的路由代价选择一条最优路径。你还可以添加多路径策略、基于随机选择的策略,甚至是基于实时RTT估算的自适应策略——这通常是研究者扩展实验的重点区域。
缓存部分由CS管理。ndnSIM默认的缓存替换算法是LRU(Least Recently Used,最近最少使用)。如果你要做缓存算法相关的实验,可以通过继承CsEntry或调整Cs的替换策略接口来自定义算法,比如LFU(最不经常使用)或随机替换。
应用层则是通过ndnSIM的App类实现的,官方例子中提供了consumer和producer类型的预置应用。我们可以直接使用,也可以编写自己的应用类来模拟更复杂的业务场景,比如视频点播、物联网数据采集等。
4. 第一个仿真脚本的完整实现
4.1 场景设计与参数设定
我们要搭建的场景非常简单:两个节点,节点0是消费者,节点1是生产者,两者通过一条点对点链路直接连接。链路带宽设为1Mbps,延迟10ms。消费者每隔1秒发送一个Interest,请求内容名为/example/data的数据。
这个场景虽然简单,但它已经涵盖了NDN通信的核心链路,后续你在真实拓扑中做的实验,本质上都是这个场景的复杂化扩展。
4.2 编写仿真脚本
在ns-3目录下,新建一个文件:scratch/ndn-first.cc。这个路径很关键,NS-3和ndnSIM约定俗成地都会把自定义脚本放在scratch目录下,这样waf构建系统能自动识别并编译。
完整的代码和详细注释如下:
// ndn-first.cc #include "ns3/core-module.h" #include "ns3/network-module.h" #include "ns3/point-to-point-module.h" #include "ns3/ndnSIM-module.h" using namespace ns3; int main(int argc, char* argv[]) { // 模拟时间设置为10秒 Config::SetDefault("ns3::PointToPointNetDevice::DataRate", StringValue("1Mbps")); Config::SetDefault("ns3::PointToPointChannel::Delay", StringValue("10ms")); Config::SetDefault("ns3::DropTailQueue<Packet>::MaxSize", StringValue("10p")); // 创建两个节点 NodeContainer nodes; nodes.Create(2); // 建立点对点连接 PointToPointHelper p2p; p2p.Install(nodes); // 在节点上安装NDN协议栈 ndn::StackHelper ndnHelper; ndnHelper.SetDefaultRoutes(true); ndnHelper.InstallAll(); // 消费者应用:每隔1秒发送一个Interest ndn::AppHelper consumerHelper("ns3::ndn::ConsumerCbr"); consumerHelper.SetPrefix("/example/data"); consumerHelper.SetAttribute("Frequency", StringValue("1")); consumerHelper.Install(nodes.Get(0)); // 生产者应用:处理Interest并返回Data ndn::AppHelper producerHelper("ns3::ndn::Producer"); producerHelper.SetPrefix("/example/data"); producerHelper.SetAttribute("PayloadSize", StringValue("1024")); producerHelper.Install(nodes.Get(1)); // 开始模拟 Simulator::Stop(Seconds(10.0)); Simulation::Run(); Simulation::Destroy(); return 0; }这里提醒一下,如果你的ndnSIM版本是2.8之前的,头文件的路径可能有差异,遇到编译错误时优先对照examples目录下的官方文件修改。
4.3 关键代码段的运行分析
上面这段代码,我需要展开讲几个关键点:
SetDefaultRoutes(true)会在每个节点上自动为所有内容前缀设置默认路由。它等价于每个节点知道“不知道往哪走的时候,就发给默认Face”。在单链路场景下,这个设置省去了手动配置FIB的麻烦,但你在多节点拓扑中模拟真实路由时,必须配合GlobalRoutingHelper来手动宣告路由前缀,否则Interest包可能会因为没有路由而被丢弃。
ConsumerCbr是“恒定比特率消费者”应用,它按照固定频率发送Interest。SetAttribute("Frequency", StringValue("1"))表示每秒发送一个请求。你可以把频率改成100来模拟高并发场景,这会影响网络的拥塞情况。
Producer应用收到任意匹配前缀的Interest包后,会回复一个指定PayloadSize大小的Data包。注意,生产者的Prefix是精确前缀匹配的,不会向下匹配子前缀。
4.4 编译与运行结果验证
运行脚本之前,先让waf配置并编译:
./waf然后执行仿真:
./waf --run ndn-first如果一切正常,终端会打印出模拟事件的信息。但默认日志级别并不展示每个包的流转细节,所以我们需要开启NDN协议栈的日志模块来观察细节:
./waf --run "ndn-first --logging=ndn.Consumer:ndn.Producer"开启之后,你会看到类似下面的日志:
0.000000000s ndn.Consumer:SendInterest(): node(0) Send Interest packet for /example/data ... 0.000014320s ndn.Producer:OnInterest(): node(1) Received Interest packet for /example/data ... 0.000014321s ndn.Producer:OnInterest(): node(1) Sending Data packet for /example/data ... 0.000024183s ndn.Consumer:OnData(): node(0) Received Data packet for /example/data看到这四行日志,说明整条NDN链路已经完全跑通了。从日志时间戳你可以算出往返时延约为24ms,这比我们设定的10ms链路延迟高了一些,主要原因是节点本身的协议栈处理耗时(包括队列延迟和调度开销),在真实实验中这个数值你可以作为一个基准参考。
4.5 进阶:接入真实网络拓扑
如果你不想只模拟两个点,官方examples里有一个ndn-grid的例子,可以创建网格拓扑。以3x3网格为例,代码逻辑大约如下:
NodeContainer nodes; nodes.Create(9); // 使用GridHelper创建网格拓扑 GridHelper grid; grid.Create(3, 3, nodes); // 安装协议栈和路由 ndn::StackHelper ndnHelper; ndnHelper.SetDefaultRoutes(true); ndnHelper.InstallAll(); // 使用GlobalRoutingHelper设置内容生产者前缀 ndn::GlobalRoutingHelper ndnGlobalRoutingHelper; ndnGlobalRoutingHelper.InstallAll(); ndnGlobalRoutingHelper.AddOrigin("/example/data", nodes.Get(8)); ndnGlobalRoutingHelper.CalculateRoutes();注意,一旦接入真实拓扑,就必须通过GlobalRoutingHelper显式宣告内容前缀,并且调用CalculateRoutes()计算路由表,SetDefaultRoutes只适合所有节点连接在一条线或完全互联的场景。
5. 数据包流转跟踪与结果分析
5.1 利用Trace文件统计关键指标
仅仅跑通脚本并不够,做实验必须量化分析。ndnSIM提供了成体系的trace功能,让我能记录实验过程中每个节点的缓存命中情况、Interest超时情况、Data包到达情况。在脚本中加入下面这些代码,就可以打开trace记录:
ndn::L3RateTracer::InstallAll("rate-trace.txt", Seconds(0.5)); ndn::CsTracer::InstallAll("cs-trace.txt", Seconds(0.5)); ndn::AppDelayTracer::InstallAll("app-delays-trace.txt");上面这些命令会分别生成三个trace文件:
- rate-trace.txt:记录每个节点每0.5秒的Interest和Data包收发数量。
- cs-trace.txt:记录内容存储的命中与未命中次数,这是分析缓存效果的核心数据。
- app-delays-trace.txt:记录应用层的请求延迟分布,包括最小、最大、平均延迟等统计值。
这些trace文件都是以TAB分隔的文本,直接用Excel或Python的pandas就能读取。
5.2 用Python快速解析Trace文件
我一般会用Python写个简单脚本快速可视化。这里给出一个提取app-delays-trace.txt中延迟数据并画图的小例子:
import pandas as pd import matplotlib.pyplot as plt df = pd.read_csv("app-delays-trace.txt", sep="\t") df.columns = ["time", "node", "type", "seq", "delay_s", "delay_us", "hop_count", "type2"] # 以延迟均值按时间点聚合 grouped = df.groupby("time")["delay_s"].mean() grouped.plot() plt.xlabel("Simulation Time (s)") plt.ylabel("Average Delay (s)") plt.title("Interest-Data Round Trip Delay") plt.grid(True) plt.savefig("delay.png", dpi=150)这样我们就能直观看出网络时延是否随着运行时间上升,如果网络发生拥塞,延迟曲线一般会显著上升,这时就需要考虑调整链路带宽、队列大小或者请求频率。
5.3 利用日志定位转发细节
跟踪文件可以告诉我们“发生了什么”,但解释“为什么发生”往往需要更细粒度的日志。ndnSIM内部使用NS-3的日志系统,你可以选择性的开启以下模块的日志来监控细节:
- ndn.Cs:查看CS的查找和插入行为。
- ndn.Pit:查看PIT条目的创建与删除。
- ndn.Face:查看Face的收发包情况和队列丢包。
- ndn.Forwarder:查看转发引擎内部对每个包的完整处理流程。
./waf --run "ndn-first --logging=ndn.Forwarder:ndn.Cs:ndn.Pit"日志会输出大量的转发细节,对调试自定义策略会很有用。但日常做实验时日志越少越好,日志太多会严重影响仿真速度,甚至会因为IO瓶颈改变仿真的执行顺序,导致时间测量不准。我一般只在抓问题的时候开启日志,跑数据的时候全部关掉。
6. 常见问题与排查技巧实录
6.1 编译阶段的典型报错
编译ndnSIM时,我最常遇到的问题是头文件缺失和模板报错。下面列几个我在实际安装中遇到的典型场景:
“fatal error: boost/shared_ptr.hpp: No such file or directory”:这是boost库没有正确安装。解决方案是执行sudo apt install libboost-all-dev,并确保版本不要太旧。
“undefined reference to typeinfo for ns3::Object”:这个常见于gcc版本与NS-3版本不兼容导致RTTI信息异常。解决方法是用gcc-11或gcc-9配合g++-11一同切换。
编译过程中内存不足导致进程被Killed:NS-3和ndnSIM的编译确实比较吃内存,实测2G内存的虚拟机编译会失败。解决办法是在编译前临时增加swap空间,或者一次只编译一个模块,比如用./waf build --targets=ndnSIM。
6.2 运行时报错的定位思路
编译通过之后,仿真脚本运行阶段的问题通常更隐蔽。这里讲一个我记忆深刻的案例:
有次在一个3x3网格拓扑中跑实验,发现部分消费者的请求延迟异常高,而且cache命中率几乎为0。最初我猜测是CS容量设置太小,但调大之后没有任何改善。后来开启Forwarder日志才发现,Interest包根本没有到达生产者,原因是GlobalRoutingHelper的部分前缀宣告被覆盖,导致路由表不完整。
排查这一类问题,我的方法基本是三步:
- 确认每个节点的FIB是否包含目标前缀的路由条目,方法是打开ndn.Fib日志。
- 确认Interest包是否到达中间节点的应达Face,方法是打开ndn.Forwarder日志观察转发的入接口和出接口。
- 确认是否出现PIT条目衰减或超时异常,这种情况经常发生在链路延迟较高而Interest超时时间(InterestLifetime)设置过短的场景。
6.3 仿真性能优化经验
ndnSIM的仿真速度在大规模拓扑下会明显变慢。如果你在跑一个上百节点的拓扑,仿真10秒可能需要实际几分钟,甚至更久。性能优化方面我总结了几点:
- 尽量编译成优化版本。配置的时候使用./waf configure --build-profile=release,速度会比debug版本快3倍以上。
- 关闭不必要的日志输出。所有日志模块开着的话,光日志写入就能让仿真速度下降一个数量级。
- 调整仿真粒度。如果不需要记录每个包的事件,可以适当调低tracer的采样频率,比如从0.1s改为1s。
- 使用并行调度。NS-3支持多线程仿真,配置时启用--enable-mpi,并把脚本中Simulator::SetNumberOfThreads设为大于1(注意某些NDN的回调不是完全线程安全的,需要实测确认)。
6.4 结果不符合预期时的检查清单
如果实验跑出来的结果明显不符合NDN的理论预期,我通常依次检查这几个地方:
- 是否开启了默认路由,同时又在用GlobalRoutingHelper?这两个机制同时存在时可能造成路由冲突。
- CS容量是否设置合理?CS容量过小缓存命中率必然低;容量过大内存会暴涨。
- 消费者应用的InterestLifetime是否足够覆盖端到端往返时延?如果设置的超时时间小于RTT,每个请求都会超时重发,导致网络大量冗余流量。
- 节点的队列长度是否够用?链路拥塞时队列丢包会严重影响请求成功率。
这些检查项,我整理成了一张速查表,放到实验笔记本上贴墙,每次排查问题都对照看一遍,节省了不少时间。
7. 从跑通到变通:下一步扩展方向
当你能够顺利跑通上述仿真,并理解代码背后的事件流转逻辑后,你实际上已经掌握了ndnSIM的基本功。接下来可以根据自己的研究方向选择深入方向:
首先是缓存策略的研究。ndnSIM默认的LRU在真实场景中并不一定最优。你可以继承Cs类替换缓存条目淘汰逻辑,对比LFU、FIFO、随机替换在不同内容流行度分布下的表现。这个方向的实验在信息中心网络领域很有热度。
其次是转发策略研究。你可以修改或继承ForwardingStrategy类,实现基于SFC(服务功能链)或基于深度强化学习的动态转发决策。ndnSIM允许你在兴趣包转发前或Data包到达后插入自定义的回调逻辑,这种灵活性是它优于其他模拟器的重要一点。
再就是多路径传输和拥塞控制。在真实网络环境中,Interest包和Data包可能经由不同路径传输,ndnSIM对多Face环境下的并发转发也有较好的支持。你可以在网格拓扑中设置多个消费者和多个生产者,观察多路径策略对网络吞吐和延迟的影响。
如果你打算做和真实环境结合的实验,ndnSIM还提供了与Mini-NDN互通的方案。Mini-NDN能直接在真实Linux网络命名空间里运行NDN协议栈,两者结合可以做到“仿真中验证大规模场景,真实环境中验证可落地性”的互补效果。
从我个人经验来说,下一步最高性价比的操作是:先把官方examples里的ndn-grid和ndn-tree这两个拓扑例子吃透,能改参数、能看懂结果曲线,然后在此基础上开始改代码做自己的实验。不要一上来就写几百行的自定义策略,那样只会让你陷入调试地狱。
最后分享一个小技巧:阅读ndnSIM源码时,不要从根目录的“大头文件”开始,而是找一个具体的处理函数,比如Forwarder::OnInterest(),通过IDE的“查找引用”功能往上下游逐级追踪调用关系。这个逆向阅读法比从头看到尾效率高得多。每读通一条调用链,你对ndnSIM的理解就越深一层,后面做代码修改时也就越来越顺手。