前言
在前面的文章ndnSIM学习(四)——examples之ndn-simple.cpp超详细剖析中,我们逐行剖析了 ndn-simple.cpp
这个example的代码逻辑。其中消费者consumer和生产者producer是整个代码运行的重点。
在前面的文章ndnSIM学习(九)——从consumer发兴趣包到producer返回data包的全过程中,我们剖析了整个兴趣包的发包收包过程。今天,就让我们一起来看看消费者consumer和生产者producer都在做些什么吧
因为producer的代码相比consumer的代码更简单,仿照庖丁解牛,我们从难啃的硬骨头间的空隙(producer)出发,来分析apps的工作流程。
ndn-producer.hpp
~/ndnSIM/ns-3/src/ndnSIM/apps/ndn-producer.hpp
文件中定义了ndnSIM中生产者(producer)类的定义,代码如下(我手动加了一些注释,直接看注释应该就能看懂了吧,不详细讲了)——
class Producer : public App {
public:
// 接口,返回关于本类对象的TypeId信息,方便外部配置
static TypeId GetTypeId(void);
// 构造函数,每当构造一个对象时,日志里输出相关信息
// e.g. 输出[+0.000000000s 2 ndn.Producer:Producer()]
Producer();
// 收到interest包后,装载一个data包(内部有编码为block的成员),最后把任务交接给到m_appLink->onReceiveData
// 后续详见下面的流程图
virtual void OnInterest(shared_ptr<const Interest> interest);
protected:
// 调用App创建相关的App管理接口,如AppLinkService这种东西,并把管理链路层和传输层的接口放到Ndn Stack里
virtual void StartApplication(); // Called at time specified by Start
// m_active = false并关闭m_face
virtual void StopApplication(); // Called at time specified by Stop
private:
Name m_prefix; // 前缀
Name m_postfix; // 后缀
uint32_t m_virtualPayloadSize; // 一个包装载数据byte数
Time m_freshness; // Fresh time
uint32_t m_signature; // SignatureValue
Name m_keyLocator;
};
发包收包过程流程图
ndn-consumer.hpp
~/ndnSIM/ns-3/src/ndnSIM/apps/ndn-consumer.hpp
文件中定义了ndnSIM中消费者(consumer)类的定义,代码如下(我手动加了一些注释,直接看注释应该就能看懂了吧,不详细讲了)——
class Consumer : public App {
public:
// 接口,返回关于本类对象的TypeId信息
static TypeId GetTypeId();
// 构造函数以及虚析构函数(便于派生)
Consumer();
virtual ~Consumer(){};
// 收到消费者返回的Data包,执行相应的处理与回显,并更新RTT(Round-Trip Time)
virtual void OnData(shared_ptr<const Data> contentObject);
// 如果Not ACK,显示对应的信息与原因
virtual void OnNack(shared_ptr<const lp::Nack> nack);
// 超时就重传(RTT翻倍)
virtual void OnTimeout(uint32_t sequenceNumber);
// 发interest包,该函数由ScheduleNextPacket设置事件触发
// 该函数给interest包填充信息后把任务交接给m_appLink->onReceiveInterest准备向自己的链路层发interest
void SendPacket();
// 在兴趣包实际发送之前的准备工作
virtual void WillSendOutInterest(uint32_t sequenceNumber);
public:
typedef void (*LastRetransmittedInterestDataDelayCallback)(Ptr<App> app, uint32_t seqno, Time delay, int32_t hopCount); // 函数指针类型
typedef void (*FirstInterestDataDelayCallback)(Ptr<App> app, uint32_t seqno, Time delay, uint32_t retxCount, int32_t hopCount); // 函数指针类型
protected:
// 调用App创建相关的App管理接口,如AppLinkService这种东西,并把管理链路层和传输层的接口放到Ndn Stack里
// 创建完执行ScheduleNextPacket,调用派生类的发包控制函数
virtual void StartApplication();
// m_active = false并关闭m_face
virtual void StopApplication();
// 发包的调度策略,如Cbr就是以恒定速率发包(纯虚函数,由子类实现)
virtual void ScheduleNextPacket() = 0;
// 检查数据包是否需要超时重传
void CheckRetxTimeout();
// 设置检查重传超时的频率
void SetRetxTimer(Time retxTimer);
// 获取当前检查重传超时的频率
Time GetRetxTimer() const;
protected:
Ptr<UniformRandomVariable> m_rand; // 随机数nonce生成器
uint32_t m_seq; // 标志目前的请求序列数值seq
uint32_t m_seqMax; // 最多请求多少个seq
EventId m_sendEvent; // 发包对应的事件Id,在子类的发包事件中会返回一个发包事件Id,当App结束时,将会取消掉该事件
Time m_retxTimer; // 重传时间
EventId m_retxEvent; // m_retxEvent对应的事件Id
Ptr<RttEstimator> m_rtt; // RTT估计器
Time m_offTime; ///< \brief Time interval between packets
Name m_interestName; // 就是ndn-simple.cpp设置的Prefix
Time m_interestLifeTime; // 就是ndn-simple.cpp设置的LifeTime
// 省略部分定义
RetxSeqsContainer m_retxSeqs; // 重传序列,每当超时的时候,就将目标序列号加入该重传序列
SeqTimeoutsContainer m_seqTimeouts; // 超时序列号+时间
SeqTimeoutsContainer m_seqLastDelay;//
SeqTimeoutsContainer m_seqFullDelay;
std::map<uint32_t, uint32_t> m_seqRetxCounts;
TracedCallback<Ptr<App> /* app */, uint32_t /* seqno */, Time /* delay */, int32_t /*hop count*/>
m_lastRetransmittedInterestDataDelay;
TracedCallback<Ptr<App> /* app */, uint32_t /* seqno */, Time /* delay */,
uint32_t /*retx count*/, int32_t /*hop count*/> m_firstInterestDataDelay;
};
ndn-consumer-cbr.hpp
ndn-simple.cpp
里的消费者是 ConsumerCbr
,其中我推测 Cbr
的含义是 Constant BitRate
的缩写,所以 ConsumerCbr
其实就是以常数速率发包的消费者。这里我对这种类型的消费者进行一些讲解,还是老规矩,看代码注释,我不说废话。
class ConsumerCbr : public Consumer {
public:
static TypeId GetTypeId();
ConsumerCbr();
virtual ~ConsumerCbr();
protected:
// 发包规则:以常数频率Frequence发interest
virtual void ScheduleNextPacket();
// 设置随机性规则,可以选择均匀分布、泊松分布
void SetRandomize(const std::string& value);
// 获取随机规则
std::string GetRandomize() const;
protected:
double m_frequency; // 发包频率
bool m_firstTime; // 标志是不是第一次请求
Ptr<RandomVariableStream> m_random; // 随机数生成器
std::string m_randomType; // 随机的规则
};
其他消费者
因为基类 Consumer
的函数 ScheduleNextPacket
是纯虚函数,所以消费者一定得用其派生类,比如前面的 ConsumerCbr
就是一个派生类。这里列举一下ndnSIM的消费者(我没细看,可能有错误,仅供参考)
ConsumerBatches
用于批量管理消费者,下面管理了一个消费者的节点链表。一旦ConsumerBatches::StartApplication
被执行,整个类就会为它管理的所有节点执行ConsumerBatches::ScheduleNextPacket
。其中每过m_rtt->RetransmitTimeout()
就会触发该消费者的Consumer::SendPacket
ConsumerCbr
是以恒定速率发送兴趣包请求的消费者。ConsumerZipfMandelbrot
继承了ConsumerCbr
,发包服从Zipf-Mandelbrot律,我也不知道是什么玩意。ConsumerWindow
似乎是以滑动窗口的方式执行拥塞控制的消费者,我没有仔细看里面的代码,所以并不确定。代码里提到本类是高度实验性的,要小心使用ConsumerPcon
继承了ConsumerWindow
类,参考了论文https://dl.acm.org/citation.cfm?id=2984369