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

第1篇_客户端写完了_为什么我还要在PLC里写一个MQTTBroker

Abstract

这一篇是 Broker 系列的开场。前一个系列我们把 PLC 作为 MQTT Client 怎么连接、发布、订阅、ACK、重发讲完了;这一篇开始反过来问:如果现场只有几台 HMI、上位机、调试工具和 PLC,真的每次都必须额外部署 EMQX / Mosquitto 吗?

前面我们已经把 PLC 做 MQTT Client 这件事拆开讲完了。

但现场很快会冒出第二个问题:

如果只是几台 HMI、一个上位机、一个调试工具和一台 PLC 之间互发消息,真的必须再摆一台外部 Broker 吗?

先给结论:

可以不摆。
但前提是我们非常清楚:PLC 里的 Broker 不是 EMQX 的复刻版,而是一个固定资源、轻量、可诊断、面向小规模工业现场的 MQTT 消息分发器。

这一篇只解决 4 个问题:

  1. PLC 侧 Broker 的定位到底是什么。
  2. 它和 EMQX / Mosquitto 的边界在哪里。
  3. 为什么 Broker 比 Client 难的地方不是 PUBLISH,而是多客户端调度。
  4. 这个系列后面会把哪些硬东西讲透。

一、从 Client 到 Broker,视角完全反过来了

Client 系列回答的是:

PLC 怎么主动连接一个 Broker。

Broker 系列回答的是:

PLC 能不能自己承担一个小型 Broker 的角色。

这不是把客户端代码反过来写一遍。Broker 的复杂度来自“别人都来找你”。

Client 侧最核心的是“一条连接怎么跑通协议链”。
Broker 侧最核心的是“多条连接怎么被稳定调度”。

这个差异非常关键。


二、PLC Broker 不是什么

先把边界说清楚,后面才不会写歪。

MqttBroker不是:

不是什么为什么不能这么定位
PLC 版 EMQXPLC 扫描周期、内存模型、任务调度和通用服务器完全不同
海量连接服务器默认目标是5~8个客户端,不是几千上万连接
完整 MQTT 5.0 企业 Broker当前只做 MQTT 5.0 基础兼容,不实现完整属性系统
云平台消息中间件不做集群、持久化数据库、WebSocket、TLS、规则引擎
把所有逻辑塞进一个 FB 的玩具必须拆连接槽位、Codec、Router、QoS Scheduler、诊断

正确定位是:

面向小型工业现场的 PLC 内置轻量 MQTT Broker。

这句话里每个词都有意义:

关键词含义
PLC 内置Broker 运行在 PLC 工程里,不依赖外部服务器
轻量固定资源、固定数组、无动态内存
工业现场关注稳定、诊断、可维护,不追求互联网级吞吐
MQTT Broker支持客户端连接、订阅、发布、路由、QoS、Retain、Will、KeepAlive

三、为什么现场会需要这个东西

典型现场并不总是云边端一整套架构。

很多时候只是这样:


这类现场的诉求很朴素:

  1. 不想为了几条消息再部署一台工控机。
  2. 不想让调试依赖外部服务是否启动。
  3. 希望 PLC 自己就能做本地消息分发。
  4. 希望出了问题能在 PLC 在线变量里直接看到原因。

这就是 PLC 侧 Broker 的价值。


四、架构上不能偷懒

一个能用的 Broker 至少要拆成这几层:

这里最容易犯的错误是:以为会回CONNACK,会转发PUBLISH,Broker 就差不多了。

不是。

真正麻烦的是:

模块真实难点
TCP 接入多客户端都连同一个端口,接入后要分配独立槽位
连接槽位每个客户端都要有独立 TCP 句柄、ClientID、KeepAlive、收发队列
CodecMQTT 是 TCP 流协议,半包、粘包、多帧同读都必须处理
RouterTopic Filter 不是字符串相等,+#都要匹配
QoSPacketId 是连接作用域,转发时不能直接沿用发布者的 PacketId
Retain勾选 Retain 后新订阅者要立即收到最后值
Will正常 DISCONNECT 和异常断线必须分开
性能高频小消息不能一帧一次 TCP_Write 慢慢挤
诊断现场不能只看客户端日志,PLC 里要有快照和历史

五、当前 MqttBroker 已经具备的能力

当前MqttBroker面向 CodeSys V3.5,核心能力如下:

能力状态说明
MQTT 3.1.1支持主链路版本
MQTT 3.1基础连接兼容支持MQIsdp + level 3
MQTT 5.0基础兼容跳过属性长度,返回零属性响应
QoS0 / QoS1 / QoS2支持主链路闭环
多客户端同端口支持多客户端同时连接1883
Retain支持写入、覆盖、清除、新订阅补发
Will支持异常断线触发
KeepAlive支持1.5 倍宽限清理
基础认证支持固定用户表
Topic ACL支持轻量前缀规则
诊断快照支持每连接在线观察
性能优化支持批量编码 + TCP 粘包写出

边界也要说清楚:

不支持当前原因
TLS当前版本只做 TCP 明文 MQTT
WebSocketPLC 侧轻量 Broker 暂不引入
集群不符合当前小规模工业定位
数据库持久化当前 Retain / 会话状态为 PLC 内存模型
完整 MQTT 5.0 属性系统当前只做基础兼容接入

六、ST 代码入口先认准这三个对象

本系列后面会不断回到源码。第一篇先认入口。

对象作用
PRG_MqttBrokerDemo最小运行示例,用户工程从这里调用 Broker
FB_MqttBrokerBroker 主功能块,负责监听、接入、路由、调度、诊断
GVL_MqttBroker全局容量和调度参数,比如端口、槽位数、队列长度、批量写帧数

最小运行形态大概是这样:

PROGRAM PRG_MqttBrokerDemo VAR fbBroker : FB_MqttBroker; // MQTT Broker 主功能块实例 bEnable : BOOL := TRUE; // 示例总使能 END_VAR fbBroker( bEnable := bEnable, sBindIP := '0.0.0.0', uiPort := GVL_MqttBroker.cnDefaultPort);

如果 PLC 网口是192.168.20.100,也可以绑定指定网卡:

fbBroker( bEnable := TRUE, sBindIP := '192.168.20.100', uiPort := 1883);

现场调试时我更建议先用0.0.0.0验证监听,再逐步改成指定网卡。


模型边界与验证路径

这一篇的核心判断,不是“PLC 一定要当 Broker”,而是:

在小规模、本地化、可诊断优先的工业现场里,PLC 侧轻量 Broker 是一个合理选项。

结论分级如下:

结论可信度依据边界
PLC Broker 不等同于 EMQX / Mosquittohigh产品架构和 PLC 固定资源模型不讨论通用服务器能力
多客户端本地消息分发适合轻量 Brokermedium工业现场常见小规模通信模型客户端规模、消息频率和 PLC 任务周期要核验
当前系列应围绕槽位、状态机、路由和诊断展开highBroker 源码结构和已测试功能后续如果加入持久化或 TLS,结构需要再扩展

验证路径也很简单:

  1. 用两个客户端同时连接 PLC 的1883
  2. 观察uiActiveSlotCount是否稳定为 2。
  3. 互相发布订阅同一主题。
  4. 再测试 Retain、QoS、KeepAlive 和断线恢复。

能跑通只是第一层。真正要看的是:连续运行、异常断开、客户端重连以后,状态还能不能保持一致。


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

  1. PLC Broker 不是 EMQX 复刻版,而是小型工业现场的本地消息分发器。
  2. Client 难在一条连接跑通,Broker 难在多条连接稳定调度。
  3. 多个客户端连同一个1883端口是正常机制,不是端口冲突。
  4. Broker 的核心不是 PUBLISH,而是连接槽位、订阅表、QoS 事务、Retain 和诊断。
  5. PLC 侧 Broker 必须固定资源、预算化调度、可在线诊断。

下篇预告

下一篇讲最容易踩坑的一关:

写 MQTT Broker,第一关不是 PUBLISH,而是怎么让多个客户端稳稳连上同一个端口。

我们会把TCP_ServerTCP_Connection、客户端槽位、xTcpAcceptActive闪烁、uiAcceptFreeSlot跳变这些真实现场问题拆开。


完整 ST 代码

下面这段是最小可运行入口,来自PRG_MqttBrokerDemo.st。它说明这套 Broker 对用户侧不是“到处散落的一堆方法”,而是周期调用一个主 FB,把监听 IP 和端口交给FB_MqttBroker后,连接接入、报文解析、订阅路由和事务调度都由内部状态机完成。

/// ======================================================================= /// 名称 : PRG_MqttBrokerDemo /// 功能 : MQTT Broker 最小运行示例 /// 说明 : 在 PLC 应用任务中周期调用本程序即可启动轻量 Broker。 /// 编程人员 : ControlRookie /// 时间 : 2026-05-08 /// 版本 : V1.0 /// ======================================================================= PROGRAM PRG_MqttBrokerDemo VAR fbBroker : FB_MqttBroker; // MQTT Broker 主功能块实例,负责监听、接入、路由和事务调度 bEnable : BOOL := TRUE; // 示例总使能,TRUE 时启动 Broker,FALSE 时停机释放运行态 END_VAR // === IMPLEMENTATION === fbBroker( bEnable := bEnable, sBindIP := '0.0.0.0', uiPort := GVL_MqttBroker.cnDefaultPort);

容量、端口、队列和超时策略统一收敛在GVL_MqttBroker。这也是 PLC 侧写 Broker 时非常重要的一点:资源边界必须显式,不要把动态增长留给运行时“自由发挥”。

/// ======================================================================= /// 名称 : GVL_MqttBroker /// 功能 : MQTT Broker 全局常量 /// 说明 : 所有容量、协议默认值和超时策略集中定义,便于 PLC 项目按资源统一裁剪。 /// 编程人员 : ControlRookie /// 时间 : 2026-05-08 /// 版本 : V1.0 /// ======================================================================= {attribute 'qualified_only'} VAR_GLOBAL CONSTANT cnDefaultPort : UINT := 1883; // Broker 默认 MQTT TCP 监听端口号 cnMaxClientSlots : UINT := 8; // PLC 侧 Broker 同时允许保持的最大客户端槽位数量 cnMaxSubscriptions : UINT := 64; // 全局订阅表最大条目数,所有客户端共享 cnMaxRetainedMessages : UINT := 32; // Retain 保留消息表最大条目数 cnMaxRxInflight : UINT := 16; // 入站 QoS>0 事务表容量,为未来 QoS2 接收去重预留 cnMaxTxInflight : UINT := 32; // 出站 QoS>0 事务表容量,为 QoS1 重发和未来 QoS2 投递预留 cnMaxTopicLen : UINT := 256; // MQTT Topic Name / Topic Filter 最大长度[byte] cnMaxClientIdLen : UINT := 128; // MQTT ClientID 最大缓存长度[byte] cnMaxUsernameLen : UINT := 64; // MQTT 用户名最大缓存长度[byte] cnMaxPasswordLen : UINT := 64; // MQTT 密码最大缓存长度[byte] cnMaxPayloadLen : UINT := 1024; // 单条消息载荷最大缓存长度[byte] cnRxBufferSize : UINT := 2048; // 单连接 TCP 接收缓冲区容量[byte] cnTxBufferSize : UINT := 2048; // 单连接 TCP 发送缓冲区容量[byte] cnMaxTopicItemsPerPacket : UINT := 8; // 单个 SUBSCRIBE / UNSUBSCRIBE 报文最多解析的主题条目数 cnMaxAuthUsers : UINT := 8; // 固定用户表最大条目数,用于轻量基础认证 cnMaxAclRules : UINT := 16; // 固定 Topic 权限表最大条目数,用于轻量 ACL cnProtocolQueueSize : UINT := 8; // 单连接协议优先队列容量,PUBACK/SUBACK/PINGRESP 等优先使用 cnDeliveryQueueSize : UINT := 16; // 单连接普通投递队列容量,PUBLISH 业务消息使用 cnDeliveryQueueHighWater : UINT := 12; // 单连接普通投递队列高水位,超过后进入慢客户端保护 cnMaxTxFramesPerWrite : UINT := 8; // 单次 TCP_Write 最多合并写出的 MQTT 控制报文帧数量;现场实测常见为 1~2 帧,保留 8 作为上限但不主动堆大突发[帧] cnMinTxBufferFree : UINT := 64; // 批量编码时保留的发送缓冲安全余量,避免最后一帧贴边写入[byte] cnDiagHistorySize : UINT := 32; // 诊断环形历史最大条目数 cnDefaultKeepAlive : UINT := 60; // 客户端未声明时采用的默认 KeepAlive 周期[s] END_VAR

系列导航

  • 系列定位:第 1 篇
  • 上一篇:MqttClient 系列收官
  • 下一篇:写 MQTT Broker,第一关不是 PUBLISH,而是怎么让多个客户端稳稳连上同一个端口

项目与资料

  • 开源项目名称:MqttBroker
  • 前置系列:MqttClient_V2_0
  • 适用平台:CodeSys V3.5
  • 典型客户端:MQTTBox、MQTTX、通信猫、HMI、上位机、边缘网关

适合谁收藏

  • 正在做 CodeSys / PLC / MQTT 项目的人
  • 想把 PLC 从 MQTT Client 扩展成轻量 Broker 的人
  • 正在排查 MQTTBox、通信猫、MQTTX 连接异常的人
  • 想学习 Broker 侧状态机、路由和 QoS 事务的人
http://www.jsqmd.com/news/946328/

相关文章:

  • 数字IC面试官最爱问的Verilog signed问题,除了规则还有这些实战考点
  • 2026年知名的广州番禺专业公司注册/广州番禺极速公司注册/广州番禺高效公司注册老客户推荐 - 品牌宣传支持者
  • 终极指南:DeepSeek-V2-Lite本地部署全流程,单卡40G GPU轻松运行
  • Anylogic智能体建模进阶:手把手教你用‘空间与网络’模块构建动态装备交互仿真
  • 从DB9接头到差分信号:手把手拆解RS232/485/422,搞懂硬件通信的底层逻辑
  • 深入GTX收发器内部:从8B/10B编码到时钟恢复,手把手教你用IBERT进行信号完整性分析
  • Appium Inspector保姆级配置教程:从Desired Capabilities到连接真机/模拟器
  • DeepXDE终极指南:5分钟掌握科学机器学习,让物理方程求解变得简单
  • Multilingual-E5-Large完全指南:如何快速上手多语言文本嵌入模型
  • 数据结构:第2讲:线性表
  • BQ4050电量计I2C通信避坑指南:当芯片手册地址遇上硬件自动左移
  • 计算机毕业设计之基于Python的微博热点新闻舆情分析与可视化
  • Simulink生成DLL时遇到的‘玄学’崩溃?我踩过的坑和终极避坑指南
  • 城市区域火灾概率推演工具:基于贝叶斯网络的Python可运行分析包
  • 从零搭建本地 Hermes Agent,一套整合包搞定自动化智能应用部署
  • 芯片热潮引爆韩国股市跻身全球第六,但泡沫隐忧渐显
  • 2026年10款降AI率平台实测:最高AI率100%直降至0.12%
  • 告别音频接口混乱:用FPGA实现16通道TDM音频传输的保姆级教程(基于48kHz/32bit)
  • 避开Arduino控制好盈电调的三个常见坑:从模拟PWM到定时器中断的优化之路
  • Unity杀戮尖塔风分层地牢生成器:自动布房+智能连通路径Demo
  • 别再乱搜代码了!Arduino Uno控制好盈电调的正确姿势(附寄存器版PWM详解)
  • 告别 Photoshop 插件:纯代码实现 QML 仪表盘的动态变色与交互(附完整工程)
  • STM32F407模拟SMBus读取BQ40Z50电量,我踩过的坑和调试心得(附完整代码)
  • 风电塔架风速与风荷载时程生成MATLAB工具包(含升阻力系数模块)
  • FFT/IFFT性能对决:递归 vs 迭代,谁才是C/C++项目中的效率王者?(附Benchmark测试)
  • 新手避坑指南:告别office破解版,用快马AI制作你的第一个文档工具
  • 超越默认编辑器:用QStyledItemDelegate为你的Qt表格打造专业级数据录入体验
  • [智能体-233]:传统的基于LLMchain langchain与基于LCEL langchain,在已定义的chain基础之上增加记忆功能的方式上的区别?
  • 示波器函数/任意波形发生器直流电源 | SiC/GaN 宽禁带半导体器件动态特性测试
  • 磁盘寻道时间计算与调度算法(FCFS、SSTF、SCAN、C-SCAN)