当前位置: 首页 > news >正文

第4篇_SUBSCRIBE不是存个字符串_Broker怎么维护订阅表通配符和多客户端路由

订阅成功了,但发布后收不到消息。

这个问题在 Broker 开发里非常常见。

原因通常不是客户端没订阅,而是 Broker 把 SUBSCRIBE 当成了“存一个字符串”。

先给结论:

客户端发布的是 Topic Name,客户端订阅的是 Topic Filter。
Broker 必须保存 Topic Filter,并在每次 PUBLISH 时用 Topic Name 去匹配所有 Filter。


一、Topic Name 和 Topic Filter 不是一个东西

这张表先记住:

名称出现位置是否允许通配符示例
Topic NamePUBLISH不允许CodeSysdevice/1/status
Topic FilterSUBSCRIBE允许CodeSysdevice/+/statusdevice/#

很多问题都出在这里。

如果客户端订阅:

device/+/status

然后另一个客户端发布:

device/plc01/status

Broker 不能做字符串相等判断。
它必须做 Topic Filter 匹配。


二、订阅链路完整流程

SUBSCRIBE 至少要做 4 件事:

  1. 解析 PacketId。
  2. 逐项读取 Topic Filter 和请求 QoS。
  3. 校验 Topic Filter 合法性。
  4. 每一项生成 SUBACK 返回码。

三、Topic Filter 校验规则

通配符不是随便放。

Filter是否合法原因
CodeSys普通主题
device/+/status+占一个层级
device/##位于最后一层
device/#/status#必须是最后一个层级
device/a++必须独占一个层级
device/#abc#必须独占一个层级
``空 Filter 不合法

所以 Broker 里需要独立函数:

F_MqttIsValidTopicFilter(sTopicFilter := sFilter)

不要把这个校验散落在 SUBSCRIBE 解析里。


四、订阅表应该保存什么

当前轻量 Broker 使用固定资源模型,不搞动态链表。

订阅表可以理解为:

字段说明
xUsed表项是否有效
uiSlotIndex属于哪个客户端槽位
sClientId客户端标识,便于诊断
sTopicFilter订阅过滤器
eMaxQoS客户端请求的最大 QoS
udiLastUpdateMs最近更新时间

结构关系大概是:


五、匹配规则示例

Topic FilterTopic Name是否匹配
CodeSysCodeSys
CodeSysCodeSys/a
CodeSys/#CodeSys
CodeSys/#CodeSys/a/b
+/statusplc01/status
+/statusarea/plc01/status
device/+/statusdevice/plc01/status
device/+/statusdevice/plc01/run/status

核心函数就是:

xMatch := F_MqttTopicMatch( sTopicFilter := stSubscription.sTopicFilter, sTopicName := stPublish.sTopicName);

这类函数必须写得很克制:层级扫描、通配符规则、边界长度,别为了几台客户端搞复杂树结构。


六、多 Topic SUBSCRIBE 不能只回一个结果

客户端可能一次订阅多个主题:

SUBSCRIBE PacketId=7 1. CodeSys QoS0 2. device/+/status QoS1 3. bad/#/topic QoS1

Broker 应该返回多个结果:

Topic Filter结果
CodeSysGranted QoS0
device/+/statusGranted QoS1
bad/#/topicFailure

所以 SUBACK 不是简单一个成功码。

90 05 00 07 00 01 80

拆开看:

字节含义
90SUBACK
05Remaining Length
00 07PacketId
00第 1 项 Granted QoS0
01第 2 项 Granted QoS1
80第 3 项 Failure

七、ST 代码入口

代码入口作用
FB_MqttBrokerCodec.M_ParseSubscribe解析 SUBSCRIBE,提取 PacketId 和 Topic Filter
FB_MqttBrokerRouter.M_AddSubscription写入或更新订阅表
FB_MqttBrokerRouter.M_FindNextRoutePUBLISH 到来时查找下一个命中订阅者
F_MqttIsValidTopicFilter校验 Topic Filter 合法性
F_MqttTopicMatch判断 Topic Name 是否命中 Topic Filter

逻辑可以压缩成:

IF F_MqttIsValidTopicFilter(sTopicFilter := sFilter) THEN fbRouter.M_AddSubscription( uiSlotIndex := uiSlotIndex, sClientId := sClientId, sTopicFilter := sFilter, eMaxQoS := eRequestQoS); byReturnCode := byGrantedQoS; ELSE byReturnCode := 16#80; END_IF

八、现场排障表

现象先看什么可能原因
客户端显示订阅失败SUBACK 返回码Topic Filter 非法或 ACL 拒绝
订阅成功但收不到订阅表是否存在该 FilterRouter 没写入或写错槽位
普通主题能收,通配符收不到F_MqttTopicMatch+/#规则实现错误
多主题订阅只有第一项生效uiSubItemCount解析循环只处理第一项
取消订阅后还收到订阅表清理UNSUBSCRIBE 没删除对应 Filter

模型边界与验证路径

SUBSCRIBE 的本质是路由规则注册,不是字符串保存。

从模型上看,Topic Filter 是规则,Topic Name 是事实。Broker 的职责就是把事实拿去匹配规则,再把结果映射到客户端槽位。

结论可信度依据验证路径
Topic Name 和 Topic Filter 必须分开处理highMQTT 订阅与发布语义device/+/status订阅,再发布device/plc01/status
通配符校验应独立封装high源码可维护性和边界复用用合法 / 非法 Filter 对比 SUBACK 返回
当前固定表路由适合小规模 PLC Brokermedium当前目标是5~8个客户端增加客户端和订阅项后观察扫描周期和队列水位

如果以后订阅规模明显扩大,问题就不再是“这个函数怎么写”,而是订阅索引模型要不要升级。当前文章不把树形索引作为默认方案,因为这不是当前轻量 Broker 的主要瓶颈。


九、这一篇你最该记住的 5 句话

  1. PUBLISH 使用 Topic Name,SUBSCRIBE 使用 Topic Filter。
  2. Broker 路由不能只做字符串相等,必须支持+#
  3. 多 Topic SUBSCRIBE 必须逐项返回 SUBACK。
  4. 订阅表要绑定客户端槽位,而不是只保存主题字符串。
  5. 订阅成功不等于路由正确,现场一定要看订阅表和匹配函数。

下篇预告

下一篇讲 PUBLISH。

重点是:

PUBLISH 不是收到就转发,Broker 必须处理 QoS、PacketId 和多客户端 fanout。

我们会重点拆 PacketId 为什么不能直接沿用发布者的。


完整 ST 代码

本篇涉及的完整代码入口:

  • MqttBroker/Device/Application/POUs/FBs/FB_MqttBrokerCodec.M_ParseSubscribe.st
  • MqttBroker/Device/Application/POUs/FBs/FB_MqttBrokerRouter.M_AddSubscription.st
  • MqttBroker/Device/Application/POUs/FBs/FB_MqttBrokerRouter.M_FindNextRoute.st
  • MqttBroker/Device/Application/POUs/Functions/F_MqttIsValidTopicFilter.st
  • MqttBroker/Device/Application/POUs/Functions/F_MqttTopicMatch.st

系列导航

  • 系列定位:第 4 篇
  • 上一篇:CONNECT 解析别写死:MQTT 3.1、3.1.1、5.0 为什么会让 Broker 反复断开
  • 下一篇:PUBLISH 不是收到就转发:Broker 怎么处理 QoS、PacketId 和多客户端 fanout

项目与资料

  • 开源项目名称:MqttBroker
  • 前置系列:MqttClient_V2_0
  • 核心关键词:SUBSCRIBE、Topic Filter、通配符、订阅表、路由

适合谁收藏

  • 订阅显示成功但收不到消息的人
  • 想把 MQTT 通配符匹配写对的人
  • 正在实现多客户端路由的人
  • 想从 Client 视角切到 Broker 视角的人
http://www.jsqmd.com/news/900842/

相关文章:

  • 从pnpm报错到Vite打包优化:手把手解决JeecgBoot-Vue3项目启动与构建的那些坑
  • 还在靠人肉发版?真正的 DevOps 平台,凌晨3点都能自己干活
  • 【MATLAB源码-第450期】基于MATLAB的GMSK调制系统中IQ相干、差分、鉴频与Viterbi解调算法对比仿真
  • Claude Code + DeepSeek V4 Pro +VS Code 安装
  • Java 做 AI 提取任务时,为什么我更建议先想好结构化输出
  • NASM到底怎么用 汇编转机器码实战详解
  • DDrawCompat:让经典DirectX游戏在现代Windows系统重获新生的完整指南
  • FlashAttention与信息检索:让AI秒找答案
  • 第5篇_PUBLISH不是收到就转发_Broker怎么处理QoS_PacketId和多客户端fanout
  • 陕西旅游酒店 GEO 服务市场深度调查:AI 搜索优化格局与真实服务真相
  • 你还在手动写脚本,别人已经用智能体跑完回归测试了
  • Cartographer无里程计建图实战:室内外效果对比与参数调优心得
  • AI智能体培训后可以做什么工作?这7个方向值得关注
  • GMS1.4 YYC编译的游戏,如何安全地修改游戏内文字?(附UndertaleModTool实战)
  • 2026世界杯洛杉矶SoFi体育场:50亿造价的天价足球圣殿
  • 《超简单:用 Python 让 Excel 飞起来》读书笔记:1.2.1 安装 Python 官方编程环境 IDLE
  • 2026年广州空调安装/清洗/移机/加雪种/拆装/维修/深度清洗/中央空调清洗/杀菌消毒/拆洗推荐:专业技术与省心服务口碑之选 - 品牌企业推荐师(官方)
  • 【多无人机集群控制11】鲁棒编队跟踪仿真,滑模与PID对比,MATLAB例程
  • 第6篇_Retain_Will_KeepAlive_工业现场为什么不能只会转发PUBLISH
  • 别再只用disp了!Matlab里fprintf格式化输出实战,从%f到%f\n的保姆级指南
  • 从Arduino到ESP32:搞定3.3V/5V混接通信,这几种电平转换电路你试过吗?
  • 把 ZipVoice 从 onnxruntime 移植到 MNN —— 7 个让人怀疑人生的细节
  • 别只改my.cnf了!深入解读MariaDB密码策略与general_log审计的取舍与最佳实践
  • 别再只盯着RGB了!搞懂CIE 1931 XYZ和Yxy,你的图像处理才算入门
  • ProxySQL选型实战:从手写读写分离到中间件的踩坑全记录
  • Grok生成的pdf怎么导出 “AI导出鸭”不会搞算我输!
  • ChatGPT饮食建议生成器上线倒计时:最后48小时必须完成的3项合规改造(GDPR+《互联网诊疗监管办法》双达标清单)
  • Louvain算法实战:用NetworkX和Python分析你的社交网络好友圈子
  • Win11Debloat:3分钟完成Windows 11终极优化与深度清理的免费神器
  • 到处听见韬τ定律