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

📅 2026/7/31 2:04:27 👁️ 阅读次数 📝 编程学习
第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 视角的人