...

一、嵌入式系统中的通信

在嵌入式Linux项目中,进程间通信有多种方案:

  • 消息队列(POSIX MQ):API繁琐,消息大小受限(通常8KB),跨机器通信需要重写
  • 共享内存+信号量:性能最好,但需要自己处理同步,稍有不慎就死锁
  • Socket编程:灵活但代码量大,光是错误处理和重连逻辑就得写上百行
  • D-Bus:功能强大但太重,一个守护进程就要几MB内存

这些方案都需要:关心太多底层细节

在智能网关、边缘计算设备这类项目里,通常会把系统拆成多个进程:采集进程读传感器数据,处理进程做算法,上报进程负责云端通信。

想要一个既能进程间通信,又能跨网络通信的统一接口?想要自动重连和负载均衡?自己实现的成本太高,可以考虑nanomsg。

https://github.com/nanomsg/nanomsg

nanomsg是一个实现了几种 可扩展协议 的高性能通信库;可扩展协议的任务是定义多个应用系统如何通信,从而组成一个大的分布式系统。

nanomsg的定位

nanomsg不是ZeroMQ的简化版,它是ZeroMQ作者Martin Sustrik的重新设计。核心理念只有一个:提供高层次的消息模式,隐藏底层传输细节

用一句话概括:把Socket编程提升到"消息通信模式"的抽象层

关键数据:

  • 二进制大小:静态链接约300KB
  • 运行时开销:每socket约4KB内存

二、使用示例

假设你有一个温湿度采集器,需要把数据同时发给本地显示进程、日志进程和网络上报进程。

Publisher端(采集进程):publisher.c

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <nn.h>
#include <pubsub.h>

// 模拟传感器读取
float read_temperature() {
    static float temp = 25.0;
    temp += (rand() % 20 - 10) / 10.0
    return temp;
}

int main() {
    int pub = nn_socket(AF_SP, NN_PUB);
    if (pub < 0) {
        perror("nn_socket");
        return 1;
    }
    
    if (nn_bind(pub, "ipc:///tmp/sensor.ipc") < 0) {
        perror("nn_bind");
        return 1;
    }
    
    printf("温度采集器已启动,发布到 ipc:///tmp/sensor.ipc\n");
    
    while (1) {
        char msg[32];
        float temp = read_temperature();
        int len = snprintf(msg, sizeof(msg), "TEMP:%.2f", temp);
        
        if (nn_send(pub, msg, len, 0) < 0) {
            perror("nn_send");
        } else {
            printf("发布: %s\n", msg);
        }
        sleep(1);
    }
    
    nn_close(pub);
    return 0;
}

Subscriber端(任意订阅进程):subscriber.c

#include <stdio.h>
#include <string.h>
#include <nn.h>
#include <pubsub.h>

int main() {
    int sub = nn_socket(AF_SP, NN_SUB);
    if (sub < 0) {
        perror("nn_socket");
        return 1;
    }
    
    // 订阅所有消息(空字符串表示无过滤)
    if (nn_setsockopt(sub, NN_SUB, NN_SUB_SUBSCRIBE, ""0) < 0) {
        perror("nn_setsockopt");
        return 1;
    }
    
    if (nn_connect(sub, "ipc:///tmp/sensor.ipc") < 0) {
        perror("nn_connect");
        return 1;
    }
    
    printf("订阅进程已启动 (PID: %d)\n", getpid());
    
    char buf[64];
    int bytes;
    while ((bytes = nn_recv(sub, buf, sizeof(buf), 0)) >= 0) {
        buf[bytes] = '\0'
        printf("[PID %d] 收到: %s\n", getpid(), buf);
    }
    
    perror("nn_recv");
    nn_close(sub);
    return 1;
}

编译:

gcc publisher.c -o publisher -lnanomsg
gcc subscriber.c -o subscriber -lnanomsg

运行:

# 启动发布者
./publisher &

# 启动多个订阅者
./subscriber &
./subscriber &
./subscriber &

说明:

  1. Publisher无需知道有几个订阅者:发送方和接收方完全解耦
  2. 传输层可切换:把ipc://改成tcp://192.168.1.100:5555就能跨机器通信
  3. 自动管理连接:订阅者断线重连是自动的,不需要你写代码

对比传统Socket代码:

如果用原始Socket实现同样功能,可能需要:

  • 监听端口 → socket() + bind() + listen()
  • 管理多个客户端连接 → accept() + 连接列表
  • 循环发送给所有客户端 → 遍历连接,处理EPIPE错误
  • 处理客户端断线 → 清理连接列表

三、nanomsg简介

3.1目录结构

核心目录结构:

模块依赖关系:

3.2 分层架构

nanomsg采用了"协议栈式"设计,但和TCP/IP不同的是,这里的"协议"是指消息通信模式。

一次send操作的内部流转
一次send操作的内部流转

协议层做了什么?

以REQ/REP(请求-应答)模式为例,协议层保证了:

  • 严格的消息配对:一个REQ必须等到REP才能发送下一条
  • 自动重试:如果对端挂掉,请求会自动路由到其他可用服务端
  • 负载均衡:REQ端连接多个REP时,自动轮询分发

3.3 零拷贝

对于大消息(如图像数据),nanomsg提供了nn_allocmsg减少中间拷贝:

方式1:使用nn_allocmsg(推荐):

方式2:普通send

nn_sendmsg内部的两条路径:

两种方式的拷贝次数对比(以inproc传输为例):

阶段 普通send nn_allocmsg
填充数据 1次拷贝 1次拷贝
发送入队 1次拷贝 0次(传指针)
接收出队 1次拷贝 0次(传指针)

四、协议模式选择

nanomsg提供6种基本模式,该如何选择?

需求场景 推荐模式 理由
多个进程需要同一份数据 PUB/SUB 自动广播,订阅者独立
远程调用需要返回结果 REQ/REP 严格的请求-响应配对
任务需要并行处理 PUSH/PULL 自动负载均衡
需要双向专用通道 PAIR 简单高效
多节点互相通知 BUS 全互联广播
需要收集多节点响应 SURVEY 有时间限制的响应收集

90%的实际项目只需要:

  1. PUB/SUB - 传感器数据、事件通知
  2. REQ/REP - 配置管理、RPC调用
  3. PUSH/PULL - 任务队列、流水线处理

五、重要经验

5.1 性能优化的三个技巧

1. inproc优先原则

同机器进程优先用inproc://而非ipc://

2. 合理设置缓冲区

默认是128KB,高吞吐场景可调到1MB,但注意内存占用。

3. 使用nn_poll多路复用

当一个进程需要处理多个socket时:

5.2 与其它项目对比

项目 二进制大小 依赖 成熟度 适用场景
nanomsg ~300KB 稳定(1.x) 嵌入式、进程间通信
nng ~400KB 活跃开发 nanomsg升级版
ZeroMQ ~2MB libsodium 非常成熟 通用分布式系统
Mosquitto ~500KB OpenSSL 成熟 MQTT专用
  • 资源受限(<16MB内存):选nanomsg
  • 需要MQTT协议:选Mosquitto
  • 新项目且对稳定性要求不极端:选nng(nanomsg作者新作,API兼容)

总结

nanomsg解决的核心问题是把通信模式标准化。不再需要为每个项目重复实现"订阅发布"或"请求应答",就像不需要自己实现TCP一样。90%场景用PUB/SUB和REQ/REP,优先inproc传输。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐