3. Mosquitto源码结构与嵌入式裁剪
Mosquitto 的源码规模并不大,但目录划分非常清晰,理解这套布局是后续裁剪和移植的前提。拿到一份 Mosquitto 源码(以 2.x 版本为例),顶层目录大致可以分为四类:核心实现、公共接口、辅助工具、构建与文档。真正与嵌入式裁剪强相关的,集中在 src/、lib/、include/、common/ 和 config.mk 这几处。
源码目录布局与核心模块职责
lib/ 是客户端库,编译产物是 libmosquitto.so/.a,提供 mosquitto_connect、mosquitto_publish、mosquitto_subscribe 等 API,嵌入式设备作为 MQTT 客户端时主要链接它。src/ 是 broker 主程序,包含 mosquitto.c(启动与主循环)、loop.c(网络事件循环)、net.c(socket 收发)、subs.c(订阅树)、retain.c(保留消息)、persist.c(持久化)、security.c(认证与 ACL)、bridge.c(桥接)、websockets.c(WebSocket 支持)。common/ 存放 broker 与库共用的基础代码,如 util_mosq.c、packet_mosq.c、memory_mosq.c、log_mosq.c,其中 memory_mosq.c 是内存统计与自定义分配器的入口,对嵌入式内存评估很关键。include/ 放对外头文件,config.mk 是编译配置的总开关,Makefile 负责把上述模块按开关组合成最终二进制。
从职责上看,broker 可以抽象为“网络层—协议层—会话层—存储层”四层:网络层由 net.c 与 loop.c 承担,协议层由 common/packet_mosq.c 与 src/handle_*.c 系列承担,会话层由 subs.c、session_expiry.c 承担,存储层由 retain.c、persist.c 承担。裁剪的本质,就是按需保留这四层中的部分能力。
编译配置选项与功能裁剪方法
Mosquitto 的裁剪入口是 config.mk,它通过一组 WITH_* 变量控制哪些模块参与编译。常用开关及影响如下:WITH_BROKER 决定是否编译 broker,若设备只做客户端,设为 no 可省掉整个 src/;WITH_TLS 控制 OpenSSL/mbedTLS 依赖,关闭后二进制可减少数百 KB;WITH_WEBSOCKETS 关闭后不再链接 libwebsockets;WITH_BRIDGE 关闭后 bridge.c 不参与编译;WITH_PERSISTENCE 关闭后 persist.c 与 SQLite 依赖被移除;WITH_SRV 关闭后不再依赖 c-ares;WITH_DOCS 关闭可跳过 man 手册生成。此外 CFLAGS 中加 -Os 可让编译器优先优化体积,配合 -ffunction-sections -Wl,--gc-sections 可进一步剔除未引用函数。
裁剪不是简单地把开关全关掉,而要按业务能力取舍。例如一个只做“设备上报 + 订阅控制指令”的嵌入式客户端,只需要 lib/ 与 common/,配置可写成:
# config.mk 片段(示例)
WITH_BROKER=no
WITH_TLS=yes
WITH_WEBSOCKETS=no
WITH_BRIDGE=no
WITH_PERSISTENCE=no
WITH_SRV=no
WITH_DOCS=no
WITH_EC=no
CFLAGS += -Os -ffunction-sections -fdata-sections
LDFLAGS += -Wl,--gc-sections
若设备还要在本机跑一个轻量 broker 做本地消息汇聚,则保留 WITH_BROKER=yes,但可关闭 WITH_PERSISTENCE 与 WITH_BRIDGE,让 broker 只做内存转发。裁剪后建议执行 make clean && make,并用 size 或 ls -lh 对比 libmosquitto.so 与 mosquitto 的体积变化,确认开关确实生效。
内存与存储占用评估
评估分静态与动态两部分。静态占用看编译产物:size libmosquitto.so 输出 text/data/bss 三段,text 是代码段,data+bss 是全局与静态数据。以关闭 TLS、关闭 broker 的客户端库为例,text 通常在 100~200 KB 量级,开启 TLS 后会明显增加。动态占用看运行时:每个客户端连接对应一个 struct mosquitto 与若干 struct mosquitto__subhier 订阅节点,单连接基础开销约几 KB,订阅树节点随主题层级增长。保留消息与持久化消息会额外占用堆内存或磁盘,关闭 WITH_PERSISTENCE 后这部分转为纯内存,需在 max_queued_messages、max_inflight_messages 中设上限。
存储占用方面,若开启持久化,persistence_location 指向的目录会生成 mosquitto.db,其大小与保留消息、会话数量正相关;嵌入式设备建议把该目录放在可写分区,并定期清理。评估时可用 valgrind --tool=massif 或 Mosquitto 自带的 memory_mosq.c 统计接口观察峰值堆内存,再结合 /proc/<pid>/status 的 VmRSS 判断实际驻留。一个可参考的示例判断标准是:客户端库 text < 200 KB、单连接 RSS 增量 < 10 KB、无持久化时磁盘占用为 0,即可认为裁剪达到嵌入式约束。
WITH_TLS 后,mosquitto_connect 的加密连接将不可用,若设备通过公网接入 broker,不应为了省体积而牺牲传输安全;此时可改用轻量 TLS 库(如 mbedTLS)并保留 WITH_TLS=yes。