一、嵌入式系统中的通信

在嵌入式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() {
    staticfloat 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");
        return1;
    }
    
    if (nn_bind(pub, "ipc:///tmp/sensor.ipc") < 0) {
        perror("nn_bind");
        return1;
    }
    
    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);
    return0;
}

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");
        return1;
    }
    
    // 订阅所有消息(空字符串表示无过滤)
    if (nn_setsockopt(sub, NN_SUB, NN_SUB_SUBSCRIBE, "", 0) < 0) {
        perror("nn_setsockopt");
        return1;
    }
    
    if (nn_connect(sub, "ipc:///tmp/sensor.ipc") < 0) {
        perror("nn_connect");
        return1;
    }
    
    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);
    return1;
}

编译:

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

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

更多推荐