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

第6篇_Retain_Will_KeepAlive_工业现场为什么不能只会转发PUBLISH

一个 Broker 会转发 PUBLISH,只能说明它能跑通“实时消息”。

但工业现场还会问:

  • 新客户端上线后,能不能马上拿到设备当前状态?
  • 设备异常掉线,其他客户端能不能收到通知?
  • 客户端死了不发包,Broker 会不会一直占着槽位?

这三个问题对应:

Retain、Will、KeepAlive。

先给结论:

只会转发 PUBLISH 的 Broker,只能算半个 Broker。
工业现场要的是状态可恢复、异常可感知、死连接可清理。

但这里的 Retain、Will、KeepAlive 仍然服务于小型工业现场,不等同于带数据库持久化、集群复制和离线会话恢复的大型 Broker。


一、Retain 解决“最后值”问题

假设 PLC 发布:

Topic: line1/plc/status Payload: RUN Retain: TRUE

后来 HMI 才上线并订阅:

line1/plc/status

如果 Broker 支持 Retain,HMI 应该立刻收到RUN
如果不支持,HMI 只能等下一次发布。

Retain 生命周期:

Retain 表不是消息队列。
一个 Topic 只保存最后一条保留消息。


二、Retain 的操作表

操作Broker 行为
Retain=0 发布正常路由,不写 Retain 表
Retain=1 且 Payload 非空写入或覆盖该 Topic 的 Retain
Retain=1 且 Payload 为空清除该 Topic 的 Retain
新订阅命中 RetainSUBACK 后补发保留消息
Topic Filter 命中多个 Retain按预算逐条补发

现场最常见误区:

客户端勾选 Retain 发布成功,不代表 Broker 已经实现 Retain。

真正的验收方法是:

  1. 客户端 A 对CodeSys发布一条 Retain 消息。
  2. 客户端 B 重新连接并订阅CodeSys
  3. B 应该立即收到这条消息。

三、Will 解决异常离线问题

Will 是客户端在 CONNECT 时提前交给 Broker 的遗嘱消息。

如果客户端异常断线,Broker 代替它发布 Will。

关键区别:

断开方式是否触发 Will
客户端发送 DISCONNECT
KeepAlive 超时
TCP 异常断开
协议错误导致关闭通常是

这在工业现场非常有用。比如:

device/plc01/online = offline

如果 Will 再配合 Retain,就能让新上线的 HMI 也看到“最后离线状态”。


四、KeepAlive 解决死连接问题

MQTT KeepAlive 不是“定时发心跳”这么简单。

它是客户端和 Broker 对连接存活的约定:

如果在 KeepAlive 时间内没有任何控制报文,客户端应该发 PINGREQ;Broker 收到后回 PINGRESP。

Broker 判断超时时通常按 1.5 倍宽限:

如果客户端声明KeepAlive = 60,Broker 不应该 60 秒一到就立刻杀。
工业网络有抖动,1.5 倍宽限更稳。


五、Retain / Will / KeepAlive 三者会汇合

这三个功能不是孤立的。

这也是为什么 Broker 不能只在连接 FB 里“关掉 TCP”就结束。

异常关闭可能会触发 Will。
Will 可能会更新 Retain。
Retain 更新后还要路由给订阅者。


六、ST 代码入口

代码入口作用
FB_MqttBrokerRouter.M_UpdateRetain写入、覆盖或清除 Retain 表
FB_MqttBrokerRouter.M_FindNextRetain新订阅时查找匹配 Retain
FB_MqttBroker.M_ServiceConnections检查连接状态、KeepAlive、异常清理
FB_MqttBroker.M_HandlePublish处理普通发布和 Retain 更新
ST_MqttBrokerRetainedMessageRetain 表项结构

Retain 更新逻辑大概是:

IF stPublish.xRetain THEN fbRouter.M_UpdateRetain( sTopicName := stPublish.sTopicName, pPayload := stPublish.pPayload, uiPayloadLen := stPublish.uiPayloadLen, eQoS := stPublish.eQoS); END_IF

KeepAlive 检查逻辑大概是:

udiTimeoutMs := TO_UDINT(uiKeepAliveSec) * 1500; IF (udiNowMs - udiLastActivityMs) > udiTimeoutMs THEN xNeedClose := TRUE; xNeedPublishWill := xWillEnabled; END_IF

七、现场排障表

现象优先检查可能原因
Retain 勾选后新订阅收不到uiRetainCountsLastRetainTopic没写 Retain 表或没做订阅补发
Retain 清不掉Payload 长度是否为 0清除规则未实现
正常断开也发 Will是否收到 DISCONNECT正常断开标志没设置
异常断开不发 WillCONNECT 是否解析 WillWill 字段没保存
客户端死了还占槽位udiLastActivityMsKeepAlive 未检查或宽限过大

模型边界与验证路径

Retain、Will、KeepAlive 看起来是三个功能点,往上看其实是会话生命周期模型。

功能解决的边界验证路径
RetainTopic 最后状态由谁保存新客户端订阅后是否立即收到最后值
Will异常离线由谁声明拔掉客户端或异常断开后是否发布 Will
KeepAlive死连接由谁清理停止客户端发送后是否按 1.5 倍宽限清理

结论分级:

结论可信度边界
Retain 必须通过新订阅补发验证high只看发布成功不够
正常 DISCONNECT 不应触发 Willhigh这是 MQTT 会话语义
KeepAlive 超时要结合扫描周期和网络抖动设置medium不同 PLC 任务周期和客户端行为会影响观测结果

这里先不要把 Retain 理解成持久化数据库。当前轻量 Broker 保存的是 PLC 内存中的最后值,断电、下载或工程重启后的行为要按实际工程配置重新验证。


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

  1. Retain 解决新订阅者获取主题最后值的问题。
  2. Will 解决异常离线通知问题。
  3. KeepAlive 解决死连接清理问题。
  4. 正常 DISCONNECT 不应该触发 Will。
  5. Retain、Will、KeepAlive 最后都会回到 Broker 的路由和连接清理调度里。

下篇预告

下一篇讲性能优化。

重点是:

为什么 PLC Broker 会有明显延迟,以及为什么从“一帧一写”改成“批量粘包写出”能明显改善实时性。


完整 ST 代码

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

  • MqttBroker/Device/Application/POUs/FBs/FB_MqttBrokerRouter.M_UpdateRetain.st
  • MqttBroker/Device/Application/POUs/FBs/FB_MqttBrokerRouter.M_FindNextRetain.st
  • MqttBroker/Device/Application/POUs/FBs/FB_MqttBroker.M_ServiceConnections.st
  • MqttBroker/Device/Application/DUTs/ST_MqttBrokerRetainedMessage.st

系列导航

  • 系列定位:第 6 篇
  • 上一篇:PUBLISH 不是收到就转发:Broker 怎么处理 QoS、PacketId 和多客户端 fanout
  • 下一篇:为什么 PLC Broker 会有延迟?从 TCP_Write 一帧一写到批量粘包写出

项目与资料

  • 开源项目名称:MqttBroker
  • 前置系列:MqttClient_V2_0
  • 核心关键词:Retain、Will、KeepAlive、最后值、异常离线

适合谁收藏

  • Retain 发布成功但新订阅收不到的人
  • 想用 MQTT 表示设备在线离线状态的人
  • 正在处理客户端死连接的人
  • 想把 Broker 从“能转发”提升到“现场可用”的人
http://www.jsqmd.com/news/900823/

相关文章:

  • 别再只用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终极优化与深度清理的免费神器
  • 到处听见韬τ定律
  • Python 入门:初识函数
  • 告别CH340!用ESP32-S3的USB CDC功能实现零成本串口打印与调试(ESP-IDF 4.4环境)
  • 从微信抢红包到数据备份:5个真实Python小项目带你玩转schedule定时任务库
  • 人工智能-现代方法(四)
  • 【ChatGPT】电子束光刻机EBL 深度拆解、爆炸图10张、信息图10张、下位机C++、上位机C#、PLC代码框架
  • 信号处理/通信算法必看:用Wirtinger导数搞定复数域梯度下降(附Python代码)
  • 从TI杯B题到毕业设计:手把手教你复刻一个自动泊车小车(附STM32/OpenMV代码)
  • 安全攻防 - 04 GMSSL 工程介绍
  • 从‘退化因子’到‘健康指标’:给你的机器人状态估计做个‘体检’
  • ChatGPT销售话术优化:今天不重构话术逻辑,明天就被AI增强型竞品碾压——来自17家已部署企业的紧急预警
  • 网站渗透实操!从getshell到CVE提权,Linux最新内核也可提权!
  • Ambari 3.0+Kafka安全认证
  • 告别3D卷积!RAFT-Stereo如何用GRU迭代优化在Middlebury拿下第一?
  • 架构师的底层重构逻辑:面部松弛、纹路加深?用3大核心参数选对高阶胶原饮
  • 语言脑机接口解码流程对比【脑机接口恢复语言2】
  • 别让天线罩毁了你的毫米波雷达!从材料选择到壁厚计算,一份给硬件工程师的避坑指南
  • 灰子学Ai: Token与字节
  • STM32L0 LPUART串口卡死?别慌,HAL库ORE溢出错误的保姆级排查与修复指南
  • 告别纸上谈兵:用Wireshark抓包实战解析5G N2/NGAP切换全流程(附pcap文件)