第1篇_客户端写完了_为什么我还要在PLC里写一个MQTTBroker
Abstract这一篇是 Broker 系列的开场。前一个系列我们把 PLC 作为 MQTT Client 怎么连接、发布、订阅、ACK、重发讲完了这一篇开始反过来问如果现场只有几台 HMI、上位机、调试工具和 PLC真的每次都必须额外部署 EMQX / Mosquitto 吗前面我们已经把 PLC 做 MQTT Client 这件事拆开讲完了。但现场很快会冒出第二个问题如果只是几台 HMI、一个上位机、一个调试工具和一台 PLC 之间互发消息真的必须再摆一台外部 Broker 吗先给结论可以不摆。但前提是我们非常清楚PLC 里的 Broker 不是 EMQX 的复刻版而是一个固定资源、轻量、可诊断、面向小规模工业现场的 MQTT 消息分发器。这一篇只解决 4 个问题PLC 侧 Broker 的定位到底是什么。它和 EMQX / Mosquitto 的边界在哪里。为什么 Broker 比 Client 难的地方不是 PUBLISH而是多客户端调度。这个系列后面会把哪些硬东西讲透。一、从 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三、为什么现场会需要这个东西典型现场并不总是云边端一整套架构。很多时候只是这样这类现场的诉求很朴素不想为了几条消息再部署一台工控机。不想让调试依赖外部服务是否启动。希望 PLC 自己就能做本地消息分发。希望出了问题能在 PLC 在线变量里直接看到原因。这就是 PLC 侧 Broker 的价值。四、架构上不能偷懒一个能用的 Broker 至少要拆成这几层这里最容易犯的错误是以为会回CONNACK会转发PUBLISHBroker 就差不多了。不是。真正麻烦的是模块真实难点TCP 接入多客户端都连同一个端口接入后要分配独立槽位连接槽位每个客户端都要有独立 TCP 句柄、ClientID、KeepAlive、收发队列CodecMQTT 是 TCP 流协议半包、粘包、多帧同读都必须处理RouterTopic Filter 不是字符串相等、#都要匹配QoSPacketId 是连接作用域转发时不能直接沿用发布者的 PacketIdRetain勾选 Retain 后新订阅者要立即收到最后值Will正常 DISCONNECT 和异常断线必须分开性能高频小消息不能一帧一次 TCP_Write 慢慢挤诊断现场不能只看客户端日志PLC 里要有快照和历史五、当前 MqttBroker 已经具备的能力当前MqttBroker面向 CodeSys V3.5核心能力如下能力状态说明MQTT 3.1.1支持主链路版本MQTT 3.1基础连接兼容支持MQIsdp level 3MQTT 5.0基础兼容跳过属性长度返回零属性响应QoS0 / QoS1 / QoS2支持主链路闭环多客户端同端口支持多客户端同时连接1883Retain支持写入、覆盖、清除、新订阅补发Will支持异常断线触发KeepAlive支持1.5 倍宽限清理基础认证支持固定用户表Topic ACL支持轻量前缀规则诊断快照支持每连接在线观察性能优化支持批量编码 TCP 粘包写出边界也要说清楚不支持当前原因TLS当前版本只做 TCP 明文 MQTTWebSocketPLC 侧轻量 Broker 暂不引入集群不符合当前小规模工业定位数据库持久化当前 Retain / 会话状态为 PLC 内存模型完整 MQTT 5.0 属性系统当前只做基础兼容接入六、ST 代码入口先认准这三个对象本系列后面会不断回到源码。第一篇先认入口。对象作用PRG_MqttBrokerDemo最小运行示例用户工程从这里调用 BrokerFB_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结构需要再扩展验证路径也很简单用两个客户端同时连接 PLC 的1883。观察uiActiveSlotCount是否稳定为 2。互相发布订阅同一主题。再测试 Retain、QoS、KeepAlive 和断线恢复。能跑通只是第一层。真正要看的是连续运行、异常断开、客户端重连以后状态还能不能保持一致。七、这一篇你最该记住的 5 句话PLC Broker 不是 EMQX 复刻版而是小型工业现场的本地消息分发器。Client 难在一条连接跑通Broker 难在多条连接稳定调度。多个客户端连同一个1883端口是正常机制不是端口冲突。Broker 的核心不是 PUBLISH而是连接槽位、订阅表、QoS 事务、Retain 和诊断。PLC 侧 Broker 必须固定资源、预算化调度、可在线诊断。下篇预告下一篇讲最容易踩坑的一关写 MQTT Broker第一关不是 PUBLISH而是怎么让多个客户端稳稳连上同一个端口。我们会把TCP_Server、TCP_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 时启动 BrokerFALSE 时停机释放运行态 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; // 入站 QoS0 事务表容量为未来 QoS2 接收去重预留 cnMaxTxInflight : UINT : 32; // 出站 QoS0 事务表容量为 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 事务的人