先说明一下,这个问题我最近几个月被问过很多次。工厂里原来的PLC要接数据到MES,设备供应商推的是Modbus TCP,信息化部门说平台只接OPC UA,车间主任从网上看了一篇文章以后坚持要上MQTT,最后项目组里一个小伙子弱弱地问了一句:那个……TCP协议三次握手到底跟这些有没有关系?
四个词放在一起,看起来像同类,实际上完全不是一回事。Modbus、OPC UA、MQTT是应用层的通信协议,TCP/IP是它们下面那层传输协议。把它们放在一个表格里对比,就像把“用什么语言写合同”“合同里面怎么盖章”“合同用什么人送到对方手里”混在一起比较一样,选错了维度,后面全乱套。
这篇笔记把我这几年做工业数据采集、设备联网、SCADA对接、IoT平台接入过程中,关于这四种“通信方式”的实际使用经验梳理一遍。会讲清楚它们各自的定位、典型选型思路、实际配置中容易踩的坑,还会给出一套我自己常用的判断逻辑,帮助你在下一单项目里少走弯路。不管你是做PLC调试的电气工程师,还是搞上位机/SCADA的软件工程师,或者是刚入行做IoT网关开发的新人,这篇内容都值得看完。
1. 盘点之前:先把四个协议的真实定位搞清楚
1.1 为什么选型总在“没有可比性”的东西之间产生
拿到一个工业通信需求,第一件事不是选协议,而是先问清楚“通信的双方是什么角色”。
举一个典型场景。一个车间里有三台不同品牌的PLC,分别是西门子、三菱和罗克韦尔。MES系统需要采集每台PLC里面的产量、报警、设备状态。这个时候,你面对的是三个完全不同的通信链路:
- PLC内部的CPU与IO模块之间,走的是各品牌自己的背板总线,这个跟你没太大关系;
- PLC与远程IO、变频器、仪表之间的现场级通信,Siemens会用Profibus/Profinet、三菱用CC-Link,跨品牌的现场级互联是最痛苦的环节;
- PLC与上位机、SCADA、MES之间的信息级通信,Modbus TCP、OPC UA、MQTT主要发生在这里,这也是本文要谈的核心区域。
所以当有人说“我把现场的Modbus设备全换成MQTT”,或者说“OPC UA比TCP好”这类话,你就可以判断这个人大概率还在纸上谈兵。Modbus设备是物理存在,OPC UA是语义化的访问接口,MQTT是面向IoT的传输模型,TCP是它们脚下的石头。把它们放在同一维度比较,本身就是问题诞生的根源。
1.2 一条产线里的典型通信链路拆解
为了后面好说话,先给一个我在项目里反复使用的链路分层图景,你理解这个以后,选型就不难了。从下往上,通常是这样的:
设备物理层(现场仪表、变频器、传感器):这一层最常见的是4-20mA、RS-485、Modbus RTU、EtherNet/IP、Profinet等。它们解决的是“数据怎么拿到手”的问题。老的仪表不支持以太网,只能通过RS-485串口并联,然后由网关或PLC代为采集。
控制器/网关汇聚层(PLC、数采网关、工业网关):这一层负责把各种物理接口统一为IP网络可以承载的数据。PLC的串口轮询采集仪表数据,然后PLC自己以Modbus TCP Server或者OPC UA Server形式开放给上层。另一种做法是直接用一款支持Modbus RTU+TCP+MQTT的物联网网关,把仪表通过RS-485接入,网关本身作为MQTT Client把数据推到云平台去。
平台接入层(SCADA、MES、IoT云平台):这一层是你最终要把数据交给谁的问题。SCADA系统通常擅长与同厂商的PLC配合,也能作为OPC UA Client去拉取第三方设备数据。MES系统一般通过关系型数据库或接口服务对接,OPC UA在这层是主流。IoT云平台(比如阿里云IoT、AWS IoT Core、自建的EMQX集群)则几乎统一以MQTT作为设备接入协议。
理解了这三层结构,选型就变成定位问题了:你在哪两个点之间通信?通信双方是现场设备还是平台?是要拉数据(Polling)还是推送数据(Publishing)?想明白这些,下一节开始逐一拆解。
2. Modbus:老但不到老掉牙,现场底层的命脉
2.1 Modbus RTU 与 TCP 的帧结构对比
Modbus是Modicon在1979年搞出来的协议,比很多读者年龄都大。它火到今天,核心就两个字:简单。简单到什么程度?读一个保持寄存器,请求帧里面只需要从站地址、功能码、寄存器起始地址、寄存器数量、CRC校验,总共8个字节。任何单片机、PLC、网关都可以轻松实现。
先看RTU模式的串口帧结构,以读保持寄存器(功能码0x03)为例:
| 字段 | 从站地址 | 功能码 | 起始寄存器高字节 | 起始寄存器低字节 | 寄存器数高字节 | 寄存器数低字节 | CRC低字节 | CRC高字节 |
|---|---|---|---|---|---|---|---|---|
| 长度 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 | 1字节 |
实际发送时一串hex大概是01 03 00 00 00 02 C4 0B,意思是问1号从站:把从40001开始的两个保持寄存器给我读出来。从站收到后回复相应长度的数据帧,格式差不多:地址+功能码+数据字节数+数据+NCRC校验。
TCP模式则是在上面这帧数据前面加了一个MBAP报文头,共7个字节:事务处理标识符2字节、协议标识符2字节(固定为0)、后续长度2字节、单元标识符1字节。单元标识符对应RTU里的从站地址。为什么加事务处理标识符?因为TCP是长连接的,同一个socket上可能连续发多条请求,需要能区分哪个响应对应哪个请求。
实测中常见的坑都出在“寄存器地址偏移”上。Modbus协议文档里的地址用的是“数据结构编号”,PLC组态软件里显示的是“PLC侧地址”,两者之间通常有1个偏移。例如很多组态软件里的40001实际对应协议帧里的地址0x0000,40001在软件里也常写作4x1。有的工程师没搞清楚,用软件里看到的地址直减1后填入程序,结果读出来全部是错的。我见过最离谱的一次,设备供应商把寄存器编号当作Modbus协议地址直接填到上位机里,读出来的温度差了32767倍。排查了一下午,就是地址+1/-1这个细节。
2.2 轮询周期、从站响应超时与实测调参
Modbus采用的是典型的“主从请求-响应”模型。主站(通常是PLC或上位机网关)发出请求,从站(变频器、仪表)才允许回复。因此,整个系统能承载多少数据点,取决于主站的轮询策略。
轮询周期计算公式很简单,但很多人没认真做过:
每轮询一个从站所需时间 = 请求帧发送时间 + 从站响应时间(通常几十毫秒,老旧仪表可能上百毫秒)+ 线路时延
假设一个Modbus RTU总线上挂了16个从站,每个从站每轮读10个寄存器(40字节数据),波特率9600bps情况下,单次交互约在20-40ms。16个站在非优化顺序下轮询一圈,轻松超过500ms。如果你要求数据刷新周期小于200ms,那RTU总线根本扛不住。解决办法就是改成Modbus TCP,用多线程/多socket并发轮询不同从站;或者改用多通道网关,串口转TCP后再并发采集。
另外一个参数必须设置正确:响应超时时间。RTU模式下,从站的响应帧结束以后,Modbus规定有3.5个字符时间的静默期作为帧间隔。有些国产仪表对帧间隔控制得很差,主站的超时设置如果太短(<20ms),就会把正常数据判断成超时。我在调试一批多功能电表时遇到过这个情况,主站发出去的请求其实都有响应,但梯形图程序中设置的超时只有18ms,导致接收数据断断续续。后来把超时改成300ms,故障消失。注意这里不是说把超时设得越大越好,超时太大会让故障检测变慢,轮询效率变低。通常建议RTU模式下超时设为50-200ms,TCP模式下设为200-1000ms,具体还要看从站厂商手册。
2.3 调试工具怎么用:Modbus Poll / Modbus Slave 要领
说到Modbus调试,绕不开Modbus Poll(主站模拟工具)和Modbus Slave(从站模拟工具)这两个软件。经常有人在网上搜“modbus poll密钥”“modbus poll注册码”之类的东西,这里不展开,但我建议能买正版就买正版,这个软件几乎是Modbus调试的标配,功能稳定,测试效率高。
Modbus Poll用来模拟主站,配置要点有几个:
- Connection设置里选择RTU或TCP。TCP模式下建议用2000端口(Modbus TCP默认就是502端口,但Windows下非管理员权限开502端口会有问题,测试时用2000或别的端口更方便);
- Slave ID与功能码对应:03是读保持寄存器,04是读输入寄存器,01是读线圈,02是读离散输入。别把功能码选错了,很多测试失败就是因为用03去读输入寄存器;
- 地址区的显示起始地址默认为0,你可以改成1,它只是显示偏移,不会影响协议帧里的实际地址;
- 轮询时间建议设成1000ms,方便观察数据变化,调试完再按实际需要改小。
Modbus Slave则用来模拟从站,方便你在没有真实设备的情况下测试主站程序。操作技巧:在Setup里定义好寄存器起始地址和数量,再把对应的寄存器数值手动填上,就能验证上位机读回来的数据是否与填入的一致。如果你做的是网关项目,建议在这里设置合理的从站响应延迟(Reply Delay,可设为50ms),用来验证主站对慢速设备是否处理正常。
再分享一个现场排障技巧:当调不通时,先用Modbus Poll直接对设备发起请求,再把Modbus Slave挂在总线上作为干扰源。这样能快速判断问题出在设备本身、通信链路还是上位机程序里。我多次用这种“替身法”定位出设备自带组态软件的Bug。
3. OPC UA:不只是“接口协议”,而是一套信息建模框架
3.1 安全证书与端口:最常见的第一道坎
如果说Modbus是“裸奔”的协议,OPC UA就是从头到尾穿了三层防护装甲的协议。先记住它的默认端口:4840。TCP模式下,客户端要跟服务器建连,必须过三道关卡:端口可达、安全策略一致、证书受信,缺一不可。
证书问题基本上是OPC UA初次联调时最让人头疼的。服务器和客户端各自持有自己的应用实例证书,握手时双方先交换证书,然后互相校验对方是否受信任。在测试阶段,最常见的做法是“客户端把服务器的证书加入受信任列表,服务器把客户端的证书加入受信任列表”。这个操作在不同软件里位置不一样,WinCC里叫“安全配置”,Kepware里在“OPC UA Configuration”标签页,Node-RED的node-opcua是自动生成证书后放在nodes_modules下的某个pki目录里,需要手动copy到客户端的trusted目录。
实操中我建议这样处理测试环境:把服务器和客户端的安全策略都设置成“无安全(None)”,或者在UA配置里把证书验证模式设成“允许匿名连接”,先把通信打通,再逐步增加安全策略。千万别一上来就启用Basic256Sha256加用户认证,不然你根本无法判断问题出在证书、密码还是网络。
配置防火墙时,除了4840端口要放行,还要留意OPC UA服务器在进行证书交换时可能会用到的临时动态端口。老式OPC DA(COM/DCOM)需要开放135端口和一系列动态端口,而OPC UA已经改成全走4840,这是一个很大的进步。如果现场用的是Windows防火墙,记得在高级设置里增加“OPC UA”相关的入站规则。
3.2 UA信息模型:把“点号”变成“有意义的对象”
Modbus的世界里,一个寄存器地址 40001 代表什么?你不知道,设备厂商也不一定写清楚了。到了OPC UA,这个痛点被彻底解决——它不仅传输数值,还传输数据的语义、单位、质量戳、时间戳,甚至设备的结构关系。
举个例子。用OPC UA访问一台水泵,你看到的不再是“寄存器40001=45”这种孤立的数字,而是一个层次化的对象模型:水泵 → 电机 → 振动传感器 → 主轴轴承温度(数值、单位是摄氏度、质量戳是Good、时间戳是xxx)。上层系统无需知道设备内部如何映射寄存器,直接按照面向对象的方式读取数据即可。
这意味着节点ID(NodeId)比Modbus地址复杂得多。在UA地址空间里,每个节点有唯一的NodeId,有名字(BrowseName)、显示名(DisplayName)、数据类型(DataType)、读写权限等属性。浏览时,你从Objects文件夹开始,一层一层往下Browse,找到想要的变量,记录它的NodeId(形如ns=2;s=PLC1.Device1.Temperature这种字符串形式),客户端就可以按这个NodeId直接读取或订阅数据。
在WinCC做OPC UA服务器时,它会把WinCC内部变量自动映射为UA节点。C#连接OPC UA一般用OPCFoundation.NetStandard.Opc.Ua这个库,连接时要设置EndpointUrl、SecurityPolicy、ApplicationName,通常还要提供操作系统的凭据。Node-RED里的node-opcua-client连接到WinCC,只需要填上服务器地址和SecurityPolicy选择Basic256Sha256,然后手工信任证书即可。
3.3 服务器选型实测:Kepware、WinCC、独立网关哪种适合你
OPC UA服务器软件选型,观察它的开放能力:
- Kepware(现在叫KEPServerEX):老牌工业连接平台,最大的优势是驱动库极其丰富,能连接几百种PLC和仪表,然后以OPC UA Server形式统一暴露出来。它本身还能做OPC UA到MQTT的转发,新版本内置了IoT Gateway。部署在Windows上,配置简单,适合做中小项目的协议汇总层。
- WinCC:你若用的是西门子全套系统,WinCC做UA Server是顺理成章的。它不会无条件开放所有变量,需要在WinCC组态时把需要开放的变量添加到“OPC UA”发布列表里,并且按通道分类。我实测过,WinCC作为UA Server,性能稳定,但配置步骤繁琐,尤其是变量数量上千时,发布列表维护非常考验耐心。
- 独立网关硬件:比如华为、研华、映翰通等品牌的工业网关,支持把底层的Modbus RTU/TCP转成OPC UA Server。好处是不需要一台PC常驻,而且很多产品同时支持MQTT转发,边缘侧处理能力不错。缺点是CPU性能参差不齐,点数多时延迟变大,选购要提前测好。
在判断“服务器地址写什么”这个问题上,有个细节容易被忽略:OPC UA的EndpointUrl里可以包含两个地址,即applicationUri与discoveryUrl,客户端总是通过Discovery Endpoint去获取服务器列表,然后从列表里再选中真正的Endpoint连接。如果客户端提示“证书不被信任”或“连接被拒绝”,排查顺序应当是:网络能通→Endpoints能列举出来→证书是否受信→安全策略是否一致。大部分人都卡在第三、四步。
4. MQTT:从设备到云端的“发布订阅”通道
4.1 Broker/Client、主题结构与遗嘱消息
MQTT和我上面提到的Modbus、OPC UA有个本质区别:它没有一个“服务器来回答你的请求”这么一说。它采用的是发布/订阅架构,中间有一个Broker(消息代理)。设备作为Client连接Broker,发布者往某个主题(Topic)发消息,订阅者订阅这个主题就能收到。发布者和订阅者之间完全解耦,不用关心对方是谁、在哪儿、是否在线。
用生活场景类比最直观:Modbus是打电话——你说一句,对方答一句,你不打过去他就不会主动告诉你;OPC UA更像是到单位档案室去查资料——你只要拿到权限,想看哪个档案就借哪个;MQTT就是杂志订阅——你订了一份《电子工程专辑》,杂志社每期出版后就给你寄,你不需要知道印刷厂在哪里。
MQTT的主题结构类似文件目录:factory/line1/machine3/temperature。设备端只需要向这个主题发布payload,订阅端(MES、数据库服务)订阅同样的主题即可。生产环境中还要善用消息保留(Retained)和遗嘱(Last Will)。Retained消息能让新订阅的客户端立刻获取到最近的数值,而不是要等下一次数据发布;遗嘱消息则能让你知道某个设备异常掉线——设备连接时先声明遗嘱,正常断开时会发送遗嘱内容,Broker就把这个遗嘱广播出去。
4.2 QoS 0/1/2的选取与重传陷阱
MQTT的QoS机制是大家最容易误用的一环。三个级别含义如下:
| QoS级别 | 含义 | 消息可能丢失 | 消息可能重复 | 适用场景 |
|---|---|---|---|---|
| 0 | 至多一次 | 是 | 否 | 高频传感器数据(温度、压力),丢了就丢,下一次还会来 |
| 1 | 至少一次 | 否 | 是 | 设备报警、上下线事件,丢了损失大,重复可以容忍 |
| 2 | 恰好一次 | 否 | 否 | 计费、订单等关键业务消息 |
很多人在做设备到云端的数据上报时,不管什么数据一律用QoS 1,结果就是客户端逻辑如果不做去重处理,数据库里会积累大量重复数据。我在一个网关项目里就踩过这个坑:网关每5秒上报一次设备状态,用的QoS 1,当时网络有一阵子延迟,Broker把同一个消息重复投递了3遍,MES系统里同一时间戳出现了3条记录。后来把状态量这类高频数据降为QoS 0,对可靠性要求高的开关量升级为QoS 2并服务端做幂等机制,问题才解决。
另外要注意,QoS 1消息的重复和状态无关,客户端务必在业务层面做去重,最简单的办法就是使用payload里带一个唯一的消息ID或者递增序列号,收到已处理过的ID就丢弃。
4.3 服务器搭建选型与安全基线
MQTT服务器(Broker)的选型,取决于你的项目规模。个人学习或小规模测试,用Eclipse Mosquitto足够了,一个不到几兆的绿色程序,配置也很简单。单机支撑几千个设备连接问题不大。如果是生产级的工业物联网平台,建议直接上EMQX或EMQX集群,它对MQTT协议支持非常完整,可以动态创建主题、做连接鉴权、设备上下线记录、规则引擎,还能平滑扩展到上百万连接。
搭建MQTT服务器时,除了开放1883端口(明文)和8883端口(TLS),还要注意几个安全基线:
- 一定不要使用默认的
admin/public账号,或者心跳间隔设得过大。Broker需要能给断线的设备快速清理会话,否则会积累大量僵尸连接。 - MQTT的cleanSession或cleanStart标志要理解清楚。CleanSession=true意味着会话结束以后Broker不保存任何状态;false则意味着离线消息可以缓存,但代价是Broker需要维护会话状态。现场设备经常切换网络信号,建议对报警类主题开启持久会话,让设备重连以后能补收离线期间的报警消息。
- 在你的网关项目中,如果它只负责推数据给云平台,就选用MQTT;如果它还需要被上位机实时读取,那就要同时启用Modbus TCP Server或者OPC UA Server。市面上很多物联网网关是三合一的,实际项目里“Modbus采集 + MQTT上云 + OPC UA开放”是最常见组合。
5. TCP撑起的那层“看不见的地基”
5.1 三次握手与四次挥手:为什么项目里老有人说“连接不上”
TCP是一个面向连接的、可靠的字节流传输协议。它的“可靠”主要体现在三次握手建立连接、四次握手断开连接、滑动窗口与确认重传这些机制上。
三次握手的完整过程可以用一个援助电话来说:A打给B:“喂,能听到吗?”(SYN);B回答:“能听到,你那边能听到我吗?”(SYN+ACK);A回一句:“我也能听到你。”(ACK)。到这为止,连接建立,双方可以开始传数据。有人问为什么不是两次?因为两次握手无法确认A的“发送能力”和B的“接收能力”同时可靠。三次握手以后双方都知道“你行我也行”。
四次挥手则是因为TCP是全双工的,A说“我没话说了”(FIN)其实只代表A到B这个方向的数据发完了,B可能还有话要讲,所以B先回ACK确认,然后等自己的数据发完之后再发FIN。A收到FIN后回最后一个ACK,连接才彻底关闭。这里有个TIME_WAIT状态,绝大多数人不理解,A主动断开连接后,会在TIME_WAIT状态等待2个报文段最长生命周期(约2分钟)后才真正释放端口。所以在压力测试或频繁重连的场景下,你会看到本机有一大堆TIME_WAIT状态的连接,如果端口不够用,就会出现bind: address already in use或者connectex这类错误。
5.2 保活、超时与防火墙端口:系统工程的必修课
TCP本身并没有主动去检测“对端是否已经断电”这种机制。默认情况下,一个TCP连接一旦建立,没人管它就能一直躺着。操作系统提供了TCP KeepAlive机制,定时发探测包,级别一般设置2小时才探测一次。对于工业现场来说,这种默认值太“佛系”了——设备断线后,上位机可能要很久才能发现。
实际项目里我通常这样设置TCP保活参数:
- Socket层:直接对每个socket设置KeepAlive为true,设置探测前空闲时间(TcpKeepAliveTime)为10s,探测间隔(TcpKeepAliveInterval)为5s;
- 应用层:更保险的做法是自己实现心跳机制,业务层每分钟发送一个心跳包,超过3个周期未收到响应就主动断开重连。这一层的心跳比底层的TCP KeepAlive可靠得多,因为即使TCP还活着,业务逻辑也可能已经卡死了。
Linux服务器上,CentOS防火墙开放TCP端口是常见的运维操作。使用firewalld时代,开放端口要执行:
firewall-cmd --zone=public --add-port=502/tcp --permanent firewall-cmd --reload然后使用firewall-cmd --list-ports检查是否生效。Cloud厂商的安全组规则也经常是连接失败的原因,这个坑我已经见得太多了。如果你的设备能ping通服务器IP,但TCP端口连不通,先检查服务器上的防火墙状态,再检查云控制台的安全组入站规则,别一上来就怀疑程序代码。
5.3 什么情况下直接写TCP自定义协议,什么时候别自找麻烦
很多时候,产品和设备之间没有现成应用层协议,工程师准备直接用TCP传自定义格式数据。我的经验是:只有当通信双方都是自己掌控的嵌入式设备或自研上位机时,才建议直接写TCP自定义协议。如果不是,不要造轮子。
理由是TCP给你提供的只是“可靠字节流”,但字节流本身没有边界。你发了一个长度为100字节的业务包,TCP可能把它拆成60+40两个包,也可能把两个业务包合并成一个包发过去。接收方必须自己做拆包粘包处理。这部分工作本身不难,用长度前缀或分隔符方案即可,但边界情况非常多,压力测试下数据不对称、半包解析出错的问题层出不穷。与其这样,不如直接用Modbus TCP、MQTT、OPC UA这些现成的应用层协议,它们已经处理好了帧边界、数据校验、状态管理等大部分问题。
比如你要在STM32上做设备通信,本来打算自己自定义TCP帧,后来一看,裸TCP要处理粘包,还要自己定义功能码、数据域格式,那和Modbus TCP做的事情有什么区别?直接裸奔TCP自定义协议,长期维护难度直线上升,除非你的业务实在特殊(比如要传输超大文件或实时音视频),否则不值得。
6. 网关实战:OPC UA转MQTT,我看过的几种可实现路径
6.1 Node-RED方案:完整节点配置与脚本
不少人搜OPC UA转MQTT时,会看到Node-RED这个工具。Node-RED可以理解为一个可视化编程平台,在浏览器里拖拽节点、连线就能完成逻辑,非常适合做协议转换和测试原型。
先说一个“OPC UA转MQTT”的参考配置逻辑:
- 安装Node-RED后,在“管理面板”里安装node-red-contrib-opcua-server和node-red-contrib-opcua(后者是OPC UA客户端)。
- 拖一个“OPC-UA Client”节点进去,填写你的OPC UA服务器地址(如
opc.tcp://192.168.1.10:4840)、安全策略(常见为None或Basic256Sha256)。 - 拖一个“MQTT Out”节点,填写Broker的地址(如
192.168.1.20:1883)、主题(如factory/machine/temperature)、QoS。 - 将这两个节点连起来,部署后在OPC-UA Client节点上点击“订阅”要转发的变量节点,消息便会自动被推到MQTT主题。
如果做更复杂的工业协议转换,比如Modbus TCP采集、滤波处理、转换成OPC UA Server,Node-RED也能胜任。它内置了Modbus节点,再配合function节点做算法处理,完全可以做一个小型数采网关。
6.2 协议转换的原理:数据边界不是简单“搬运”
OPC UA到MQTT的转换,核心难点不是调用哪个API,而是数据模型之间的映射。OPC UA节点大量是有结构的对象,比如一个Aggregate结构由多个子变量组成;而MQTT payload只是一段字节流或JSON。转换前必须明确要发哪个变量的值、什么数据类型、单位是什么、是否带时间戳与质量戳。
我习惯的转换模板是这样的:
{ "deviceId": "PumpA", "timestamp": "2025-01-15T10:30:00.123Z", "values": { "temperature": 45.6, "vibration": 2.31 }, "quality": "Good" }注意时间戳一定是采集时刻的现场时间,不要用MQTT发出时间。质量戳则来自OPC UA服务器,表示数据是否好(Good/Bad/Uncertain)。“Bad”质量的数据是绝不能直接进生产统计的。
Node-RED中我通常会在OPC-UA Client节点后面接一个function节点,专门做数据格式组装。一个最常见的错误是直接把OPC UA返回的对象结构原封不动发给MQTT,实际MQTT接收端解析起来很痛苦,JSON里嵌套太多层,数据字段与业务字段对不上,这也是靠堆逻辑Debug难以解决的。
6.3 当心数据丢失:定时器、缓存、重传设计
OPC UA订阅(Subscription)本来就是一种周期性推送,只要服务器与节点状态正常,数据就能持续送到。但到了MQTT这一段,问题就来了:
- 如果MQTT Broker网络闪断,网关缓存的OPC UA数据就会积压;
- 如果采用QoS 0,积压的数据会在网络恢复后被瞬间发出去,产生突刺;
- 如果采用QoS 1,Rebuffer区可能会爆掉,导致前面缓存的消息被丢弃。
我的做法是:在网关节点边上加一个本地缓存容器,例如SQLite或Redis,以固定周期从OPC UA拿到数据后先写入缓存,再按自己的节奏(例如每5秒批量)以QoS 1发到Broker。Broker端订阅方收到后再按timestamp判断是否需要更新数据库。这样兼顾了实时性与可靠性。
如果不需要高精度历史数据,从中可以再做一层降采样:OPC UA端 1秒一个采样值,MQTT端 10秒聚合一个平均值即可。我在工厂做能耗监测时,曾经用1秒采样、1分钟聚合的方式,把数据量压缩了60倍,历史库压力骤减。聚合函数(平均值、最大值、累计值)放到网关里执行,比放到云平台简单得多。
7. 最终选择:我的推荐路线和踩坑清单
7.1 按应用场景打分的决策参考
这个问题没有唯一正确答案,但有一个相对通用的选择逻辑,按“通信双方”来分:
场景一:PLC → 上位机/SCADA(同一厂商或跨品牌)
推荐Modbus TCP,理由是简单、快、调试工具丰富。但如果你需要采集的数据量大、数据结构复杂,或需要实时浏览设备信息模型,建议直接上OPC UA,省得以后系统扩展时再把全部点位从Modbus翻译成UA。
场景二:设备 → MES/平台(要求语义统一)
推荐OPC UA。MES系统需要知道“温度”是摄氏度的温度,而非一个貌似温度的整型数据。OPC UA的信息模型能一并提供单位、质量、节点关系,MES二次开发起来体验非常好。
场景三:现场设备 → IoT云平台(云计算平台、手机APP、Web可视化)
推荐MQTT。云平台大多原生支持MQTT设备接入,设备端用ESP32、STM32+4G模组也好对接。MQTT非常轻量,尤其适合嵌入式设备/窄带环境。
场景四:自制设备,通信对象是自己开发的上位机——用裸TCP或UDP。因为你自己可以控制全部代码,可以省掉Modbus的数据格式封装开销,核心是必须处理好拆包粘包和心跳重连。
可以这样总结:如果你的数据消费者是人(HMI/SCADA),用OPC UA或Modbus;如果你的数据消费者是系统(云平台/数据库),用MQTT;如果你在两者之间的网关层做翻译,Modbus和OPC UA往往你都要支持。
7.2 真实项目里遇到过的一些通信坑
按回忆顺一遍我这些年碰过的问题,都跟上述协议直接相关,很多坑现在想起来还是很无语:
| 现象 | 根因 | 解决方案 |
|---|---|---|
| Modbus TCP长时间运行后故障 | 上位机socket未设置KeepAlive,网络瞬断后TCP连接只处于“半死”状态 | 应用层心跳/重连逻辑;或设置TCP KeepAlive |
| 同样一组变量,OPC UA读出来是Bad质量 | 设备与UA服务器之间的通信(e.g. Modbus轮询)超时,变量质量被置为Bad | 检查底层采集链路的响应时间,必要时增大超时或减小轮询点数 |
| MQTT消息积压,数据延迟半小时 | Broker订阅端消费太慢;QoS=1消息重复消费也拖慢了消费速度 | 降QoS、批量订阅、增加消费者实例、缓存硬盘告警 |
TCP连接失败、Connecting refused | 云服务器安全组没放行端口;或本机防火墙未放行端口 | 检查防火墙与云控制台安全组,先telnet测试端口 |
| OPC UA客户端提示“证书不受信任” | UA服务器未把客户端证书加入受信任列表 | 拷贝证书到trusted目录,重新连接 |
| Modbus读回来的数据是0x7FFF | 从站尚未初始化完成或寄存器不可用 | 检查从站状态、寄存器地址边界,防止越界读 |
这些坑没有一个是高深理论,都是配置或机制性细节,但每一个都足以让项目交付延期一两周。项目越大越要提前做好协议与工具的选型、网络规划,尤其要在联调前写好端口分配、防火墙规则、证书信任的清单。
7.3 写在最后的忠告
再说点实在的。很多初学者一听说OPC UA是工业通信的未来,就把Modbus抛到一边;一听说MQTT适合物联网,就想把现场所有PLC都改成MQTT推送。其实真实工业现场最讲兼容,Modbus、OPC UA、MQTT这三种协议各有极强的生存土壤。
我目前的习惯做法是:**现场采集层尽量统一到Modbus TCP或OPC UA,边缘网关层做协议转换,上云统一走MQTT。**这样既支持底层设备的异构接入,又能让上层平台得到干净统一的数据。TCP是这一切的地基,但也仅此而已——它在工业协议栈里更像空气:谁也不会天天提起,但一旦连接断了,几乎所有问题都会归结到它头上。
选型这事,从来不是追求“最先进”,而是追求“最匹配”。你可以从现场的设备能力、系统平台的接入方式、实施团队的维护能力这三个维度来做决策。设备端的能力往往决定了底线,平台接入方式决定了上限,而团队维护能力决定了一个系统能稳定运行多久。
希望这份盘点能让你少踩几个通信坑。如果你正在做Modbus、OPC UA或MQTT相关的项目,也欢迎在评论区聊聊你的设备选型和踩坑经历,有些坑一个人摸索真的容易心态崩。