1、发送侧数据结构:nn_dist 分发器
1.1 结构体定义与全局作用
核心作用:BUS、PUB协议通用出方向管道管理器。负责维护对外可发送的pipe链表,实现消息一对多广播、指定管道屏蔽、链路异常自动剔除。BUS发送消息完全依赖该结构,无额外发送逻辑。
c
// 单管道链表节点:绑定单个出方向管道
struct nn_dist_data {
struct nn_list_item item; // 侵入式链表挂载点,接入dist的pipes链表
struct nn_pipe *pipe; // 指向TCP/IPC物理连接管道(出方向)
};
// 分发器主体
struct nn_dist {
uint32_t count; // 就绪出管道数量,O(1)读取,避免链表遍历
struct nn_list pipes; // 就绪管道双向链表头
};
字段细化解释:
-
nn_dist_data:是管道的发送侧影子节点,一条物理pipe会单独创建dist_data,和接收侧数据结构内存独立,互不干扰
-
count:仅统计调用nn_dist_out上线的管道,已add未out的冷管道不计入
1.2 dist全套关键函数+源码精简解读
dist分为生命周期管理、节点上下线、消息发送三类函数,按调用时序排序:
1.2.1 初始化/销毁
c
void nn_dist_init (struct nn_dist *self)
{
self->count = 0;
nn_list_init (&self->pipes);
}
// 销毁强制要求无遗留节点,断言防内存泄漏
void nn_dist_term (struct nn_dist *self)
{
nn_assert (self->count == 0);
nn_list_term (&self->pipes);
}
1.2.2 管道节点上下线(冷/热切换)
c
// 冷初始化:绑定pipe指针,不加入就绪链表(冷节点,无法发送)
void nn_dist_add (struct nn_dist *self, struct nn_dist_data *data, struct nn_pipe *pipe)
{
data->pipe = pipe;
nn_list_item_init (&data->item);
}
// 上线:冷节点转为就绪节点,插入链表尾部,计入count
void nn_dist_out (struct nn_dist *self, struct nn_dist_data *data)
{
++self->count;
nn_list_insert (&self->pipes, &data->item, nn_list_end (&self->pipes));
}
// 下线:从链表删除、释放链表资源,永久销毁节点
void nn_dist_rm (struct nn_dist *self, struct nn_dist_data *data)
{
if (nn_list_item_isinlist (&data->item)) {
--self->count;
nn_list_erase (&self->pipes, &data->item);
}
nn_list_item_term (&data->item);
}
1.2.3 核心发送函数 nn_dist_send(发送唯一入口)
c
int nn_dist_send (struct nn_dist *self, struct nn_msg *msg, struct nn_pipe *exclude)
{
struct nn_list_item *it;
struct nn_dist_data *data;
struct nn_msg copy;
// 无就绪管道:直接释放消息,防止内存泄漏
if (nn_slow (self->count) == 0) {
nn_msg_term (msg);
return 0;
}
// 批量浅拷贝:基于chunk引用计数,不拷贝实际消息内存
nn_msg_bulkcopy_start (msg, self->count);
it = nn_list_begin (&self->pipes);
while (it != nn_list_end (&self->pipes)) {
data = nn_cont (it, struct nn_dist_data, item);
nn_msg_bulkcopy_cp (©, msg);
// 屏蔽指定管道,不发送
if (data->pipe == exclude) {
nn_msg_term (©);
}
else {
int rc = nn_pipe_send (data->pipe, ©);
// 管道断开:就地删除链表节点,迭代器无需后移
if (rc & NN_PIPE_RELEASE) {
--self->count;
it = nn_list_erase (&self->pipes, it);
continue;
}
}
it = nn_list_next (&self->pipes, it);
}
// 释放原始消息,副本依靠引用计数自动回收
nn_msg_term (msg);
return 0;
}
dist核心短板:发送串行遍历,无轮询、无超时,单管道阻塞会阻塞全局发送。
2、接收侧底层结构:nn_priolist 优先级轮询链表
全局作用 :nanomsg接收侧通用调度底座,所有需要公平接收的协议(BUS/SUB/SURVEY)全部复用。同时实现静态16级优先级抢占 + 同优先级RR轮询,解决多管道消息饥饿问题,是fq的底层依赖。
2.1 三层嵌套结构体逐字段说明
2.1.1 最小节点 nn_priolist_data
c
struct nn_priolist_data {
struct nn_pipe *pipe; // 入方向物理管道指针
int priority; // 管道优先级:取值1~16,1为最高优先级
struct nn_list_item item; // 挂载到对应优先级slot的链表
};
关键误区:优先级不能填0,内部slot数组下标=priority-1,0号下标对应最高优先级。
2.1.2 单优先级槽 nn_priolist_slot
c
struct nn_priolist_slot {
struct nn_list pipes; // 当前优先级所有可读管道链表
struct nn_priolist_data *current; // RR轮询游标,记录上一次读取的管道
};
作用:隔离不同优先级管道,同优先级内部通过current游标实现轮询,互不干扰。
2.1.3 顶层调度器 nn_priolist
c
#define NN_PRIOLIST_SLOTS 16
struct nn_priolist {
struct nn_priolist_slot slots [NN_PRIOLIST_SLOTS]; // 16个优先级槽位
int current; // 全局当前优先级,-1代表无可读管道
};
current规则:永远指向当前非空最高优先级槽,读取时直接访问,无需遍历全部16个槽,查询O(1)。
2.2 priolist四大核心函数源码
接收侧调用顺序:add(绑定管道) → activate(管道可读激活) → getpipe(获取可读管道) → advance(轮询切换游标)
2.2.1 nn_priolist_add:绑定管道,创建冷节点
c
void nn_priolist_add (struct nn_priolist *self,
struct nn_priolist_data *data, struct nn_pipe *pipe, int priority)
{
data->pipe = pipe;
data->priority = priority;
nn_list_item_init (&data->item);
// 仅初始化,不加入可读链表,此时节点为冷节点,无法被读取
}
2.2.2 nn_priolist_activate:冷节点激活为可读节点
触发时机:管道收到对端消息,内核触发可读事件
c
void nn_priolist_activate (struct nn_priolist *self, struct nn_priolist_data *data)
{
struct nn_priolist_slot *slot = &self->slots [data->priority - 1];
// 槽内已有管道:仅追加链表,不改动轮询游标、全局优先级
if (!nn_list_empty (&slot->pipes)) {
nn_list_insert (&slot->pipes, &data->item, nn_list_end (&slot->pipes));
return;
}
// 槽内无管道:初始化槽游标,抢占全局优先级
nn_list_insert (&slot->pipes, &data->item, nn_list_end (&slot->pipes));
slot->current = data;
// 新节点优先级更高,全局切换优先级
if (self->current == -1 || self->current > data->priority)
self->current = data->priority;
}
2.2.3 nn_priolist_getpipe:获取待读取管道
c
struct nn_pipe *nn_priolist_getpipe (struct nn_priolist *self)
{
if (self->current == -1) return NULL;
// 直接返回全局最高优先级槽的轮询游标管道
return self->slots [self->current - 1].current->pipe;
}
2.2.4 nn_priolist_advance:读取后游标轮转(轮询核心)
c
void nn_priolist_advance (struct nn_priolist *self, int release)
{
struct nn_priolist_slot *slot = &self->slots [self->current - 1];
struct nn_list_item *it;
// release=0:管道正常,仅移动游标,节点保留
// release=1:管道断开,删除节点后移动游标
if (release)
it = nn_list_erase (&slot->pipes, &slot->current->item);
else
it = nn_list_next (&slot->pipes, &slot->current->item);
// 链表末尾循环回到头部
if (!it) it = nn_list_begin (&slot->pipes);
slot->current = nn_cont (it, struct nn_priolist_data, item);
// 当前槽为空,自动降级到低优先级槽
while (nn_list_empty (&slot->pipes)) {
++self->current;
if (self->current > 16) {self->current = -1; return;}
slot = &self->slots [self->current - 1];
}
}
3、接收侧封装结构:nn_fq 公平队列
3.1 结构体与设计目的
c
struct nn_fq_data {
struct nn_priolist_data priodata; // 直接内嵌底层优先级节点,无扩展字段
};
struct nn_fq {
struct nn_priolist priolist; // 直接内嵌底层优先级调度器
};
核心作用(最容易误解):nn_fq内部没有任何自定义逻辑、没有额外内存字段,纯上层适配外壳。
设计原因:统一协议层调用接口,屏蔽priolist priority-1下标偏移、底层链表细节。BUS、SUB、SURVEY协议只需要调用fq接口,无需感知底层优先级槽实现,实现接口统一。
3.2 fq关键函数(全部为浅层封装)
c
// 管道加入接收队列,透传至priolist_add
void nn_fq_add (struct nn_fq *self, struct nn_fq_data *data,
struct nn_pipe *pipe, int priority)
{
nn_priolist_add (&self->priolist, &data->priodata, pipe, priority);
}
// 管道可读激活,透传至priolist_activate
void nn_fq_in (struct nn_fq *self, struct nn_fq_data *data)
{
nn_priolist_activate (&self->priolist, &data->priodata);
}
// 接收消息顶层入口
int nn_fq_recv (struct nn_fq *self, struct nn_msg *msg, struct nn_pipe **pipe)
{
struct nn_pipe *p = nn_priolist_getpipe (&self->priolist);
if (!p) return -EAGAIN;
int rc = nn_pipe_recv (p, msg);
if (pipe) *pipe = p;
// 根据管道状态决定是否释放节点
nn_priolist_advance (&self->priolist, rc & NN_PIPE_RELEASE);
return rc & ~NN_PIPE_RELEASE;
}
BUS专属配置:所有管道优先级固定为1,因此16级优先级全部闲置,fq直接退化为纯RR轮询队列。
4、协议粘合层:nn_xbus 原始总线协议
4.1 核心结构体:收发节点绑定
c
// 单管道收发成对节点
struct nn_xbus_data {
struct nn_dist_data outitem; // 发送侧dist节点
struct nn_fq_data initem; // 接收侧fq节点
};
// 原始总线socket主体
struct nn_xbus {
struct nn_sockbase sockbase; // nanomsg统一socket基类
struct nn_dist outpipes; // 全局发送分发器
struct nn_fq inpipes; // 全局接收公平队列
};
结构依赖关系:一条物理pipe,对应一对outitem/initem,分别挂载到outpipes/inpipes两个独立链表,收发数据完全隔离,互不抢占资源。
4.2 xbus核心生命周期函数
c
// 管道连接建立:同时初始化收发节点
int nn_xbus_add (struct nn_sockbase *self, struct nn_pipe *pipe)
{
struct nn_xbus *xbus = nn_cont (self, struct nn_xbus, sockbase);
struct nn_xbus_data *data = nn_alloc(sizeof(struct nn_xbus_data), "xbus pipe data");
// 读取管道接收优先级,BUS默认1
int rcvprio = 1;
nn_fq_add (&xbus->inpipes, &data->initem, pipe, rcvprio);
nn_dist_add (&xbus->outpipes, &data->outitem, pipe);
nn_pipe_setdata (pipe, data);
return 0;
}
// 管道可发送:上线dist发送节点
void nn_xbus_out (struct nn_sockbase *self, struct nn_pipe *pipe)
{
struct nn_xbus *xbus = nn_cont (self, struct nn_xbus, sockbase);
struct nn_xbus_data *data = nn_pipe_getdata(pipe);
nn_dist_out(&xbus->outpipes, &data->outitem);
}
// 管道可读:上线fq接收节点
void nn_xbus_in (struct nn_sockbase *self, struct nn_pipe *pipe)
{
struct nn_xbus *xbus = nn_cont (self, struct nn_xbus, sockbase);
struct nn_xbus_data *data = nn_pipe_getdata(pipe);
nn_fq_in(&xbus->inpipes, &data->initem);
}
xbus独有特性:支持消息头部携带uint64_t管道ID,发送时可以指定exclude管道,实现定向屏蔽,属于底层RAW协议,不对外暴露给用户。
5、用户层协议:nn_bus 标准总线协议
5.1 结构体继承关系
c
struct nn_bus {
struct nn_xbus xbus; // 内嵌xbus,零内存开销继承所有能力
};
设计目的:屏蔽xbus底层头部细节,面向业务用户提供极简API,不修改底层收发逻辑,仅做入参校验和报文裁剪。
5.2 bus收发重写逻辑(仅两层封装)
c
// 用户发送:强制清空消息头部,禁止exclude屏蔽
static int nn_bus_send (struct nn_sockbase *self, struct nn_msg *msg)
{
struct nn_bus *bus = nn_cont (self, struct nn_bus, xbus.sockbase);
// 禁止用户携带自定义头部,防止非法exclude
if (nn_chunkref_size (&msg->sphdr))
return -EINVAL;
// 直接透传xbus发送逻辑
return nn_xbus_send (&bus->xbus.sockbase, msg);
}
// 用户接收:删除xbus追加的源管道ID头部
static int nn_bus_recv (struct nn_sockbase *self, struct nn_msg *msg)
{
struct nn_bus *bus = nn_cont (self, struct nn_bus, xbus.sockbase);
int rc = nn_xbus_recv (&bus->xbus.sockbase, msg);
// 裁剪底层追加的管道ID头部,用户无感知
nn_chunkref_term (&msg->sphdr);
nn_chunkref_init (&msg->sphdr, 0);
return rc;
}
6、全局结构依赖总图谱(极简总结)
-
发送链路:nn_bus → nn_xbus → nn_dist → nn_dist_data → nn_pipe
-
接收链路:nn_bus → nn_xbus → nn_fq → nn_priolist → nn_priolist_data → nn_pipe