3、OPC UA协议栈剖析:UA Binary、安全模型与信息模型
好,咱们进入第三讲。这一讲我打算把OPC UA协议栈的核心三层给拆开揉碎了讲清楚。说白了,就是三个东西:数据怎么打包(UA Binary)、数据怎么安全传输(加密与认证)、数据长什么样(信息模型)。这三层搞明白了,你对OPC UA的理解就能超过八成的人。
3.1 OPC UA二进制协议(UA Binary)
先聊UA Binary。你可能听说过OPC UA支持好几种传输协议,比如HTTP、HTTPS,还有UA TCP。但在嵌入式设备上,我们几乎只用UA Binary。为什么?因为它效率高、开销小。
我刚开始做OPC UA移植时,也想过用HTTP。结果一测,一个简单的读变量请求,HTTP头部就占了上百字节。对于只有几十KB RAM的MCU来说,这简直是灾难。UA Binary就不一样了,它用紧凑的二进制编码,一个请求可能就几十个字节。
3.1.1 编码机制
UA Binary的核心思想很简单:把结构化数据变成字节流。它定义了一套规则,告诉你整数怎么编码、字符串怎么编码、数组怎么编码。
举个例子,一个32位无符号整数,在UA Binary里默认用小端序编码。但如果你觉得4字节太浪费,可以用可变长度整数(Variant Length Integer)。这个技巧我在一个内存受限的项目里用过,能省下不少空间。
// 一个简单的UA Binary编码示例(伪代码)
// 编码一个UInt32值 0x12345678
uint8_t buffer[4];
buffer[0] = 0x78; // 低字节在前
buffer[1] = 0x56;
buffer[2] = 0x34;
buffer[3] = 0x12;
嗯,这里要注意:字符串编码不是直接塞ASCII码。它前面会加一个长度前缀。比如字符串"ABC",编码后是:0x03 0x41 0x42 0x43。第一个字节是长度,后面是内容。这个设计很巧妙,解析器拿到长度就知道要读多少字节,不用傻等结束符。
3.1.2 消息结构
UA Binary的消息结构分三层:
- 消息头(Message Header):固定4字节,包含消息类型、通道ID等。
- 安全头(Security Header):如果启用了安全,这里放安全令牌、序列号等。
- 序列化体(Serialized Body):真正的业务数据,比如读请求、写请求。
我见过不少新手在解析消息时,直接跳过安全头去读体数据,结果解析出来的全是乱码。其实安全头里有个序列号字段,用来防止重放攻击。你想想看,如果攻击者截获了一个写请求,然后重复发送,设备就会反复执行同一个操作。有了序列号,服务器就能识别出重复消息并丢弃。
3.2 OPC UA安全模型(加密与认证)
安全模型这块,我得说,是OPC UA最容易被忽视但又最重要的部分。很多工程师觉得“我就在内网跑,不用加密”。但我在一个工业现场项目中就吃过这个亏——有人用Wireshark抓包,直接把PLC的变量值全看光了。
3.2.1 安全模式与策略
OPC UA定义了三种安全模式:
| 模式 | 说明 | 适用场景 |
|---|---|---|
| None | 不加密,不签名 | 调试、测试环境 |
| Sign | 只签名,不加密 | 需要验证来源,但数据不敏感 |
| SignAndEncrypt | 签名且加密 | 生产环境,数据敏感 |
我个人习惯,只要设备连了网络,至少用Sign模式。加密虽然会消耗CPU,但现在的MCU基本都有硬件加密引擎,性能损失可以接受。
3.2.2 证书与信任链
OPC UA的认证基于X.509证书。每个设备都有一个证书,里面包含公钥和身份信息。客户端和服务器建立连接时,会交换证书,然后验证对方是否可信。
我曾经在一个项目中,因为证书过期导致整个产线断连。排查了半天才发现是证书有效期到了。所以我现在做项目,都会在代码里加一个证书有效期检查,提前一周报警。
3.2.3 加密算法选择
OPC UA支持多种加密算法,比如AES-256、RSA-2048、SHA-256。在嵌入式设备上,我建议优先选择AES-256-GCM。为什么?因为它同时提供加密和完整性校验,而且硬件加速支持很普遍。
你想想看,如果用了AES-CBC,你还得单独算HMAC来保证完整性,多了一步操作。而GCM模式一步到位,省时省力。
3.3 OPC UA信息模型基础
信息模型是OPC UA的灵魂。它定义了数据在OPC UA世界里长什么样、怎么组织、怎么访问。说白了,就是一套数据字典。
3.3.1 节点与引用
OPC UA信息模型由节点(Node)和引用(Reference)组成。节点是基本单元,引用是节点之间的关系。
节点有8种类型,但最常用的是这几种:
- 对象节点(ObjectNode):代表一个实体,比如一个电机、一个传感器。
- 变量节点(VariableNode):代表一个数值,比如温度、压力。
- 方法节点(MethodNode):代表一个可调用的函数,比如启动、停止。
引用则定义了节点之间的关系。比如“电机”对象节点通过HasComponent引用指向“温度”变量节点,表示“电机包含温度这个属性”。
3.3.2 类型系统
OPC UA有丰富的类型系统。除了基本类型(Int32、Float、String等),还支持结构体和枚举。这让我在描述复杂数据时非常方便。
举个例子,一个“电机状态”变量,如果只用Int32表示,你得额外写文档说明0代表停止、1代表运行。但用OPC UA的枚举类型,直接定义成MotorStateEnum,客户端拿到数据就知道含义。
// 在OPC UA地址空间中定义枚举类型
// 伪代码示意
EnumType MotorStateEnum {
STOPPED = 0,
RUNNING = 1,
FAULT = 2
}
3.3.3 地址空间
所有节点和引用组合在一起,就构成了地址空间(AddressSpace)。你可以把它想象成一个巨大的树状结构,根节点是Root,下面挂载各种对象和变量。
我刚开始做OPC UA服务器时,地址空间设计得一塌糊涂。变量到处乱挂,客户端找数据找得想哭。后来我学乖了,先画一张地址空间结构图,理清楚层级关系再编码。这个习惯我一直保留到现在。
3.4 三者如何协同工作
好,现在我们把三个部分串起来。一个典型的OPC UA通信流程是这样的:
- 建立连接:客户端和服务器通过UA Binary握手,协商安全策略。
- 证书交换:双方交换证书,验证身份。这一步用到了安全模型。
- 创建会话:验证通过后,创建安全通道,后续所有数据都用UA Binary编码,并用协商好的算法加密。
- 访问数据:客户端通过信息模型找到目标节点,发送读请求。服务器解析请求,返回编码后的数据。
你看,这三层是环环相扣的。没有UA Binary,数据传不过去;没有安全模型,数据传得不安全;没有信息模型,数据传过去也没人看得懂。
嗯,这一讲的内容就到这里。下一讲我会带大家手写一个简单的OPC UA服务器,把今天讲的理论落地到代码里。到时候你会看到,UA Binary的编码、安全模型的配置、信息模型的构建,是怎么在嵌入式设备上一步步实现的。