☰
Mosquitto 0.9.3 发布:五个关键 Bug 修复的技术解析
2026/9/25 4:42:44 网站建设 项目流程
  • 物联网
  • 消息队列
  • 后端
  • 网络/通信

【免费下载链接】mosquitto

Eclipse Mosquitto - An open source MQTT broker

项目地址:https://gitcode.com/gh_mirrors/mo/mosquitto
点击查看免费下载

导读

Mosquitto 0.9.3 是 Eclipse Mosquitto 早期开发阶段的一个纯缺陷修复(bugfix)版本,围绕 QoS 2 保留消息状态、监听套接字地址族回退、非 clean session 重连消息保留、Python 客户端脚本兼容性与 Windows 头文件包含五个具体问题展开。本文以官方发布说明 version-0-9-3-released.md 为主线,逐条解析每个修复背后的行为差异,并结合当前仓库的 retain.c、handle_publish.c、net.c、handle_connect.c 等源码,说明这些问题在现代版本中的演进形态,帮助读者理解 MQTT broker 在消息保留、会话管理和监听器初始化上的底层机制。

发布背景:一次纯 Bugfix 迭代

Mosquitto 0.9.3 发布于 2011 年 3 月,定位是缺陷修复版本,官方说明明确其不包含任何新功能,只解决既有问题。发布说明列出了五条修复项:

  • 为 QoS 2 消息设置保留(retained)消息状态(bug #726535);
  • 仅当没有任何地址族可用时才以错误退出,而非任一地址族不可用就中止;
  • 非 clean session 客户端重连时不再清空已排队消息;
  • 让 mosquitto.py 兼容 Python 2.6 以下版本;
  • 修复 mosquitto.h 在 Windows 平台的头文件包含问题。

这些修复覆盖了 broker 的消息生命周期、监听器初始化、会话恢复以及跨平台构建兼容性。下文逐条结合源码展开。

修复一:QoS 2 消息的保留标志(bug #726535)

MQTT 的 PUBLISH 报文头中有一个 RETAIN 标志位(0x01)。当发布者以 retain=1 发布消息时,broker 不仅要把消息分发给当前订阅者,还要将其存入保留消息树,供后续订阅该主题的客户端在订阅建立时立即收到。问题 #726535 的场景是:当 QoS 2 消息经过完整的 PUBREC/PUBREL 握手之后,其保留状态没有被正确设置,导致本应进入保留消息树的 QoS 2 消息丢失了 retain 语义。

在当前源码 handle_publish.c 中,消息进入处理管线时首先解析保留位并做可用性检查:

base_msg->data.retain = (header & 0x01); if(base_msg->data.retain && db.config->retain_available == false){ ... }

随后在 retain.c 的retain__store()中,保留消息被写入一棵以主题层级组织的哈希树:

if(retainhier->retained){ if(persist && retainhier->retained->data.topic[0] != '$' && base_msg->data.payloadlen == 0){ /* Only delete if another retained message isn't replacing this one */ plugin_persist__handle_retain_msg_delete(retainhier->retained); } db__msg_store_ref_dec(&retainhier->retained); ... } if(base_msg->data.payloadlen){ retainhier->retained = base_msg; db__msg_store_ref_inc(retainhier->retained); ... }

关键点在retain__store()之前:QoS 2 消息在handle_publish.c中需要先走完去重与握手流程(handle_publish.c 中通过db__message_store_find检查已存储消息、处理dup标志),只有经过这些步骤后消息才会真正进入sub__messages_queue和retain__store。0.9.3 修复的正是这一链条上 retain 状态传递的缺口,确保 QoS 2 消息在完成完整握手后仍能携带 retain 语义进入保留消息树。

与之配套的读取路径是retain__queue()(retain.c):当客户端订阅某个主题过滤器时,broker 将订阅主题切分为 token 后在保留树上逐级搜索(retain__search),并通过retain__process按订阅 QoS 与消息 QoS 的较小值下发保留消息(retain.c):

sub_qos = sub->options & 0x03; if (db.config->upgrade_outgoing_qos){ qos = sub_qos; } else { qos = retained->data.qos; if(qos > sub_qos) qos = sub_qos; }

可见,QoS 2 的保留消息能否被正确下发,依赖写入端data.retain字段的正确传递。0.9.3 修复的 bug #726535 正是要保证这一字段在 QoS 2 握手下不被丢失。

修复二:监听套接字按地址族回退而非整体中止

在 0.9.3 之前,broker 在打开监听套接字时,如果某个地址族(IPv4 或 IPv6)不可用,就会直接以错误退出。修复后,行为变为:只有当所有地址族都无法打开时才中止启动;只要还有任一地址族可用,broker 就继续运行。

这一策略在现代版本中体现为 net.c 的net__socket_listen_tcp()实现。该函数先用getaddrinfo解析监听地址(listener->host+listener->port),然后遍历返回的地址链表ainfo:

hints.ai_family = AF_UNSPEC; /* 未指定 socket_domain 时同时尝试 IPv4/IPv6 */ hints.ai_flags = AI_PASSIVE; hints.ai_socktype = SOCK_STREAM; rc = getaddrinfo(listener->host, service, &hints, &ainfo); ... for(rp = ainfo; rp; rp = rp->ai_next){ if(rp->ai_family == AF_INET){ log__printf(NULL, MOSQ_LOG_INFO, "Opening ipv4 listen socket on port %d.", ...); }else if(rp->ai_family == AF_INET6){ log__printf(NULL, MOSQ_LOG_INFO, "Opening ipv6 listen socket on port %d.", ...); }else{ continue; } sock = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); if(sock == INVALID_SOCKET){ net__print_error(MOSQ_LOG_WARNING, "Warning: %s"); continue; /* 单个地址族失败仅告警,继续尝试下一个 */ } ... }

注意两个细节:

  1. 逐地址族容错:socket()失败时只打 Warning 并continue,不中断整个监听器创建。这正对应 0.9.3 修复说明中"不再因任一地址族不可用而中止"的语义。
  2. AI_PASSIVE与AF_UNSPEC组合:未显式配置socket_domain时(net.c),hints.ai_family = AF_UNSPEC让系统同时解析 IPv4/IPv6;只有配置了socket_domain时才强制单一地址族。

若读者在运行环境中只启用了 IPv6 或只有 IPv4 可用,现代 Mosquitto 会在日志中分别打印Opening ipv4 listen socket.../Opening ipv6 listen socket...,并继续监听成功的那一族——这正是 0.9.3 修复行为至今的延续。

修复三:非 clean session 重连不清空排队消息

MQTT 的 clean session(v3.1/3.1.1)或 clean start(v5)语义决定:客户端以 clean session=1 连接时,broker 必须丢弃该客户端此前的一切会话状态;而 clean session=0 时,broker 应保留订阅与未投递消息,等待客户端重连后继续投递。

0.9.3 修复的问题是:此前非 clean session 客户端重连时,已排队的消息被错误清空,破坏了持久会话(persistent session)的基本承诺。当前源码 handle_connect.c 对会话恢复的处理如下:

if(context->clean_start == true){ sub__clean_session(found_context); } ... if(context->clean_start == false && found_context->session_expiry_interval > 0){ ... /* v5 下按会话过期时间恢复 */ }

也就是说,只有clean_start == true才调用sub__clean_session()清除会话;clean session=0 时则保留原有订阅、入站/出站消息队列(msgs_in、msgs_out,见 context.c),使断线期间积压的消息能在重连后继续投递。

与之配套的还有db__message_write_queued_in()(handle_publish.c):broker 在完成一次 PUBLISH 处理后立即尝试冲刷该客户端排队中的入站消息。整个链路保证:会话状态只有显式的 clean session/clean start 或会话过期(session expiry,context.c)才会被清除,重连本身不再触发队列清空——这正是 0.9.3 该修复在现代版本中的语义落点。

修复四:mosquitto.py 兼容 Python 2.6 以下版本

发布说明提到让mosquitto.py兼容 Python < 2.6。这是早期 Mosquitto 随发行包携带的纯 Python MQTT 客户端脚本(后续演进为独立的paho-mqtt客户端库,仓库当前的 Python 测试客户端见 test/mosq_test.py)。当时的问题主要是脚本中使用了仅在 Python 2.6+ 才提供的语法或标准库特性(例如json模块的某些行为、字符串格式化方式),导致旧版本 Python 无法导入。

该修复属于兼容性收窄:在不引入新特性的前提下,将脚本的语法/标准库使用面收敛到 Python 2.6 之前即可解析的范围,以扩大可运行环境。对于今天的读者,其工程意义在于:开源项目在支持新 Python 版本的同时,需要明确声明并测试其最低支持版本,避免隐式依赖某个较新版本才有的语言特性。

修复五:mosquitto.h 的 Windows 头文件包含

mosquitto.h是面向用户的客户端 API 头文件(当前版本见 include/mosquitto.h)。0.9.3 之前,该头文件在 Windows 下编译时存在包含顺序或缺失包含的问题,导致 Windows 开发者无法直接 include。修复后,头文件的包含逻辑在 Windows 与 POSIX 平台上均能自洽工作。

现代版本中,include/mosquitto.h 将公开 API 拆分为多个子头文件并统一汇总:

#include <mosquitto/mqtt_protocol.h> #include <mosquitto/libmosquitto.h> #include <mosquitto/libcommon.h> #include <mosquitto/broker.h> #include <mosquitto/broker_control.h> #include <mosquitto/broker_plugin.h>

其中libmosquitto.h及各子头文件内部再按平台做差异处理(例如 common/winthread_mosq.h 与 lib/dummypthread.h 分别对应 Windows 与 POSIX 线程抽象)。这种"公开头文件自包含、平台差异下放到内部实现"的架构,正是对 0.9.3 时代"Windows 下头文件包含不完整"问题的结构性修复:用户只需 include 一个<mosquitto.h>,其余平台细节由库内部消化。

版本演进观察

0.9.3 所处的 0.9.x 阶段是 Mosquitto 走向 1.0 之前的功能收敛期。对照当前仓库可以清晰看到,这五个修复所触及的领域——保留消息(retain.c)、监听器(listeners.c 与 net.c)、会话状态(context.c、handle_connect.c)、客户端头文件(include/mosquitto.h)——在后来的 MQTT v5 支持、持久化插件化、多监听器配置等演进中不断被重构和扩展,但其核心语义(QoS 2 保留、地址族容错、持久会话不清队列)一直延续至今。

此外,仓库的回归测试体系也覆盖了这些语义的现代形态,例如:

  • 保留消息相关的测试见 test/broker 目录下的04-retain-*.py系列(如04-retain-qos0.py、04-retain-qos1-qos0.py、04-retain-upgrade-outgoing-qos.py);
  • 会话与重连相关的测试见05-clean-session-qos1.py、05-session-expiry-v5.py等。

这些测试可以视为 0.9.3 修复目标的自动化回归保障。

总结

Mosquitto 0.9.3 虽然只是一次缺陷修复发布,但其五个修复点恰好对应了 MQTT broker 最容易被忽视的语义细节:

修复项核心语义现代源码参考
QoS 2 保留状态保留位在完整握手后不被丢失handle_publish.c、retain.c
地址族回退单一地址族失败不阻断监听器net.c
非 clean session 不清队列持久会话重连保留积压消息handle_connect.c
Python <2.6 兼容声明并收敛最低运行版本test/mosq_test.py
Windows 头文件包含公开头文件跨平台自包含include/mosquitto.h

对于需要深挖行为的读者,建议结合 retain.c 的保留树遍历逻辑、net.c 的net__socket_listen_tcp()回退循环以及 handle_connect.c 的会话恢复分支进行代码级验证;这些实现正是 0.9.3 修复思想在十余年演进后的直接继承者。

  • 物联网
  • 消息队列
  • 后端
  • 网络/通信

【免费下载链接】mosquitto

Eclipse Mosquitto - An open source MQTT broker

项目地址:https://gitcode.com/gh_mirrors/mo/mosquitto
点击查看免费下载
上一篇:android-sunflower中的多模块依赖分析:项目报告生成
下一篇:vim-airline会话自动备份频率设置:详细指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询