Nanomsg 协议BUS及相关数据结构详解

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 (&copy, msg);
       // 屏蔽指定管道,不发送
       if (data->pipe == exclude) {
           nn_msg_term (&copy);
       }
       else {
         int rc = nn_pipe_send (data->pipe, &copy);
         // 管道断开:就地删除链表节点,迭代器无需后移
         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、全局结构依赖总图谱(极简总结)

  1. 发送链路:nn_bus → nn_xbus → nn_dist → nn_dist_data → nn_pipe

  2. 接收链路:nn_bus → nn_xbus → nn_fq → nn_priolist → nn_priolist_data → nn_pipe