• 正文
  • 相关推荐
申请入驻 产业图谱

一个实用的嵌入式消息库!

13小时前
142
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

做过嵌入式网络应用的朋友,大概都经历过这样的过程。

最初的需求很简单:客户端发一个请求,服务端返回结果。于是写一个 TCP Server,再约定一下消息格式。

很快,问题开始变多:

到最后,原本只想交换几条消息,却维护起了一套自己的网络框架。

如果我们不想额外部署消息中间件,又不愿继续手搓协议栈,可以看看 NNG。

NNG 是什么?

NNG 的全称是 nanomsg-next-gen。

仓库:https://github.com/nanomsg/nng/tree/stable 许可证: MIT 许可证 参考手册:https://nng.nanomsg.org/ref

它是一个使用 C 实现的轻量级、无 Broker 消息库,也是 nanomsg Scalability Protocols 的重写和延续。

所谓“无 Broker”,指的是通信节点可以直接建立连接,不需要先部署一个中心消息服务器

所谓“消息库”,则意味着 NNG 处理的不只是字节流。它向应用提供完整消息,并在协议层处理连接管理、请求匹配、重试和消息路由等问题。

使用者主要做两件事:

    选择通信模式提供连接地址

例如:

通信模式:REQ/REP
连接地址:tcp://127.0.0.1:8899

前者决定消息怎样流动,后者决定消息从哪里经过。

NNG 使用 MIT 许可证,可以构建为动态库或静态库,也可以裁剪后嵌入现有 C/C++ 项目。

第一个 NNG 程序

NNG 的构建要求不高:

    支持 C11 的编译器CMake 3.15 或更高版本推荐使用 Ninja

下面使用项目内的安装目录,不需要把文件写入系统的 /usr/local

第一步:编译 NNG

在项目根目录执行:

sudo apt update
sudo apt install -y build-essential cmake ninja-build
cmake -S . -B build -G Ninja 
  -DCMAKE_INSTALL_PREFIX="$PWD/install"
cmake --build build
ctest --test-dir build --output-on-failure
cmake --install build

完成后,库文件、头文件和 CMake Package 配置会被安装到当前项目的 install 目录。

第二步:编译 REQ/REP 示例

NNG 的 Demo 使用 find_package(nng) 查找已经安装的库,因此需要把刚才的安装目录传给 CMake:

cmake -S demo/reqrep -B demo/reqrep/build -G Ninja 
  -DCMAKE_PREFIX_PATH="$PWD/install"
cmake --build demo/reqrep/build

第三步:启动服务端

打开一个终端:

./demo/reqrep/build/reqrep 
  server tcp://127.0.0.1:8899

第四步:启动客户端

再打开一个终端:

./demo/reqrep/build/reqrep 
  client tcp://127.0.0.1:8899

可以看到类似输出:

这个 Demo 实现了一个简单的时间查询服务。

客户端发送一个 64 位 DATE 命令,服务端返回 UNIX 时间戳。传输内容使用网络字节序,而不是随意拼接字符串。

代码里还埋了一个小测试:服务端会故意跳过第六次响应,用来观察 REQ 协议的重新发送行为。

常见通信需求做成了协议

NNG 没有试图用一个万能接口覆盖所有场景,而是提供了多组语义明确的协议。

REQ/REP:请求与响应

客户端发出一个请求,服务端返回一个响应。

它适合:

REQ 协议会负责请求与响应的匹配,也可以在没有收到回复时重新发送请求。

PUB/SUB:发布与订阅

发布者发送一条消息,多个订阅者可以同时接收。

它适合:

发布者不需要维护一份订阅者列表,订阅关系由协议处理。

PUSH/PULL:任务管道

PUSH 端生产任务,PULL 端消费任务。

这个模式带有背压能力。当消费者处理不过来时,生产端不会无限制地继续灌入数据。

它适合简单的任务分发和流水线处理。

SURVEYOR/RESPONDENT:一问多答

一个节点向多个节点发起询问,并在限定时间内收集响应。

它适合:

此外,NNG 还提供 BUS 和 PAIR 等通信模式。

协议和传输

这是理解 NNG 最关键的一点。

协议负责定义消息语义:

REQ/REP
PUB/SUB
PUSH/PULL
SURVEYOR/RESPONDENT
BUS
PAIR

传输负责解决节点如何连接:

inproc
IPC
TCP
TLS
WebSocket
UDP

二者可以组合。

一个 REQ/REP 程序可以通过 TCP 跨机器通信,也可以改用 IPC 在本机进程之间通信。业务代码不需要重新实现一套请求匹配逻辑。

地址中的 URL Scheme 用于选择传输:

tcp://127.0.0.1:8899
ipc:///tmp/demo
inproc://demo
ws://127.0.0.1:8080/channel

在 NNG 内部,一个 Socket 对应一种协议。

Dialer 主动连接远端,Listener 等待连接。连接建立后形成 Pipe,消息最终通过 Pipe 在两个节点之间传递。

大多数应用只需要操作 Socket。只有需要读取连接信息、单独配置端点或实现代理时,才需要直接管理 Dialer、Listener 和 Pipe。

从应用代码看,NNG 做了什么?

一个典型的 NNG 2.x 程序会经历以下过程:

REQ 客户端使用 nng_req0_open() 创建 Socket,REP 服务端使用 nng_rep0_open()

应用不会直接获得一段不完整的 TCP 字节流。NNG 对外提供的是完整消息:一条消息要么完整交付,要么不交付。

不过,这不代表所有协议都自动提供可靠、有序投递。

具体保证取决于协议本身。例如 REQ/REP 具备请求匹配和重试机制,而 PUB/SUB 更接近尽力而为的广播。

选择协议前,必须先弄清楚业务真正需要的语义。

NNG 还能用来做什么?

仓库里的 Demo 不只有 REQ/REP。

发布订阅转发器

demo/pubsub_forwarder 使用 Raw Socket 创建消息转发节点。

发布者连接转发器前端,订阅者连接后端。多个订阅者可以同时接收发布的数据。

项目自带的 nngcat 可以直接创建 PUB、SUB、REQ、REP 等节点,适合:

    调试消息拓扑手工发送测试数据验证协议行为编写 Shell 集成测试

可以把它理解为 NNG 协议世界里的 netcat。

REST 与消息协议之间的网关

demo/rest 对外提供 HTTP 接口,内部通过 NNG REQ/REP 转发请求。

这种组合适合网关场景:

HTTP 客户端
    ↓
REST 接口
    ↓
NNG REQ
    ↓
内部 REP 服务

外部系统继续使用 HTTP,内部服务则使用消息协议通信。

这也说明 NNG 并不要求整个系统采用同一种通信技术。

进程内与进程间通信

同一个协议可以运行在不同传输上。

模块位于同一个进程时,可以使用 inproc;拆成不同进程后,可以改成 IPC;需要跨机器时,再切换到 TCP 或 TLS。

只要应用没有依赖特定传输的选项,协议层代码通常不需要跟着重写。

最值得学习

NNG 最值得研究的地方,并不是它实现了多少种消息模式,而是它如何控制一个异步 C 网络库的复杂度。

协议与传输正交组合

NNG 没有在 TCP 模块里写死 RPC,也没有为每种协议重新实现一遍连接逻辑。

协议和传输通过操作表连接。

增加一种传输时,REQ、PUB、BUS 等协议不需要修改;增加一种协议时,也不用重新实现 TCP、IPC 和 WebSocket。

这才是“可扩展”真正落到代码里的样子。

用一种异步原语贯穿所有层

如果同步、异步、协议和传输各自维护一套生命周期,C 项目很快就会陷入状态机地狱。

NNG 使用 AIO 贯穿应用接口、协议状态机、传输层和平台 I/O。同步操作只是异步操作的一种使用方式。

统一原语减少了重复逻辑,也让取消、超时和完成回调拥有一致的处理方式。

强类型句柄减少误用

nng_socketnng_pipenng_listener 和 nng_dialer 并不是可以随意混用的裸整数,而是不同的结构体类型。

这样一来,把 Pipe 句柄传给 Socket API 之类的错误,有机会在编译阶段被发现。

对于 C API,这是一种成本很低、收益很实际的安全设计。

可裁剪构建

NNG 的协议和传输拥有独立的 CMake 开关

资源受限的设备可以只保留需要的能力。例如只启用 REQ/REP、inproc 和 TCP,而不是把所有协议和传输都编进固件

项目还支持静态库和动态库,这让它比较适合嵌入式 Linux、边缘设备和现有 C/C++ 服务。

哪些项目适合使用 NNG?

总结

两个程序交换消息,通常有两个极端方案。

一个是直接使用 TCP,自己维护消息边界、重连、超时和状态机。

另一个是部署完整的消息中间件,再处理服务器、监控和运维。

NNG 位于两者之间。

它保留了 C 库可嵌入、可裁剪的特点,又把 RPC、发布订阅、任务管道、自动重连和异步并发整理成了可复用的协议组件。

相关推荐

登录即可解锁
  • 海量技术文章
  • 设计资源下载
  • 产业链客户资源
  • 写文章/发需求
立即登录

本公众号专注于嵌入式技术,包括但不限于C/C++、嵌入式、物联网、Linux等编程学习笔记,同时,公众号内包含大量的学习资源。欢迎关注,一同交流学习,共同进步!