一个嵌入式系统的进程通信利器!
嵌入式Linux系统中的进程间通信方案包括:1)共享内存实现高效数据共享;2)消息队列提供异步通信机制;3)信号用于简单事件通知;4)管道实现单向数据流;5)套接字支持跨网络通信。这些方法各具特点,开发者需根据实时性、数据量等需求选择合适方案,确保系统高效稳定运行。
...
一、嵌入式系统中的通信
在嵌入式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 &
说明:
-
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)