一个嵌入式系统进程间通信利器!
不再需要为每个项目重复实现"订阅发布"或"请求应答",就像不需要自己实现TCP一样。在智能网关、边缘计算设备这类项目里,通常会把系统拆成多个进程:采集进程读传感器数据,处理进程做算法,上报进程负责云端通信。nanomsg采用了"协议栈式"设计,但和TCP/IP不同的是,这里的"协议"是指消息通信模式。假设你有一个温湿度采集器,需要把数据同时发给本地显示进程、日志进程和网络上报进程。:API繁琐,消
一、嵌入式系统中的通信
在嵌入式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 &

说明:
-
Publisher无需知道有几个订阅者:发送方和接收方完全解耦
-
传输层可切换:把
ipc://改成tcp://192.168.1.100:5555就能跨机器通信 -
自动管理连接:订阅者断线重连是自动的,不需要你写代码
对比传统Socket代码:
如果用原始Socket实现同样功能,可能需要:
-
监听端口 →
socket() + bind() + listen() -
管理多个客户端连接 →
accept()+ 连接列表 -
循环发送给所有客户端 → 遍历连接,处理
EPIPE错误 -
处理客户端断线 → 清理连接列表
三、nanomsg简介
3.1目录结构
核心目录结构:

模块依赖关系:

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

协议层做了什么?
以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%的实际项目只需要:
-
PUB/SUB - 传感器数据、事件通知
-
REQ/REP - 配置管理、RPC调用
-
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传输。
更多推荐
所有评论(0)