工业通讯协议(四)- OPCUA(二)

Pre:工业通讯协议(四)- OPCUA(一)

OPCUA的报文头

OPCUA协议解析最标志性的是它的前3个字节,组成一个报文头。

理解OPC UA中不同的消息类型(MessageType)及其报文头结构,是分析通信过程的关键。下面这个表格汇总了核心的MessageType及其报文头的主要构成。

消息类型与报文头概览

消息类型 报文头关键字段 主要功能与描述
HEL (Hello) MessageType: HEL (3字节ASCII)
Reserved: F (1字节ASCII)
MessageSize: 整个消息长度
用于连接初始握手。客户端发送此消息,与服务器协商通信参数(如缓冲区大小、最大消息长度等),是建立连接后发送的第一条消息。
ACK (Acknowledge) 同上 服务器对HEL消息的响应,确认或调整客户端提出的通信参数,完成握手协商。
ERR (Error) 同上 用于在建立安全通道之前报告协议级别的错误(如不支持的协议版本、无效的端点URL等)。发送错误消息后,通常会关闭套接字连接。
OPN (OpenSecureChannel) MessageType: OPN
IsFinal: 指示是否为最终分块
MessageSize: 分块长度
SecureChannelId: 安全通道ID(此时为0)
客户端在握手后发送,请求建立一个安全通道。该请求会协商安全策略和模式,并获取一个有效的SecureChannelId用于后续通信。
MSG (Secure Message) MessageType: MSG
IsFinal: 指示是否为最终分块
MessageSize: 分块长度
SecureChannelId: 有效的通道ID
在已建立的安全通道内传输所有加密的应用层服务请求和响应(如读取、写入、浏览数据等)。这是安全会话建立后最主要的消息类型。
CLO (CloseSecureChannel) MessageType: CLO
IsFinal: F
MessageSize
SecureChannelId: 要关闭的通道ID
用于正常终止一个安全通道。

报文头详解与通信流程

了解了不同类型的消息后,我们进一步看看报文头的具体构成和它们在完整通信流程中的作用。

1. 通用报文头结构
所有OPC UA TCP协议消息都以一个8字节的固定头部开始:

  • MessageType (3字节): 使用ASCII码标识消息类型,例如 HEL, ACK, OPN 等。
  • Reserved (1字节): 对于HEL, ACK, ERR这些连接协议消息,此字段固定为字母F的ASCII码。对于OPN, MSG, CLO,该字段演变为Chunk Type(或IsFinal标志),用F表示最终块,C表示中间块。
  • MessageSize (4字节): 表示整个消息(包括头部和体部)的总字节数,采用小端字节序(Least Significant Byte first)。

2. 安全通道相关消息的扩展头
对于OPN, MSG, CLO这类安全会话消息,在通用报文头之后还有扩展头:

  • SecureChannelId (4字节): 由服务器分配的唯一标识符。在OPN请求中该值为0,服务器在响应中返回一个有效的ID,后续该通道的所有MSG消息都使用此ID。
  • 安全头(Security Header): 包含安全策略URI、证书等信息,用于加密和签名。
  • 序列头(Sequence Header): 包含一个单调递增的序列号(防重放攻击)和请求ID(关联请求与响应)。

3. 典型通信流程中的消息序列
一个完整的OPC UA通信会话通常按以下顺序使用这些消息类型:

  1. 连接握手:客户端 -> HEL -> 服务器;服务器 -> ACK -> 客户端。
  2. 建立安全通道:客户端 -> OPN -> 服务器;服务器 -> OPN(响应)-> 客户端(分配SecureChannelId)。
  3. 应用会话与数据交换:在安全通道内,通过MSG类型消息进行(如创建会话、读取数据、发布订阅等)。
  4. 关闭通道:客户端 -> CLO -> 服务器。

OPCUA示例详解

下面是一个简单的基于python的opuca服务器和客户端:

from opcua import ua, Server
import time

# 创建服务器对象
server = Server()

# 设置服务器端点(URL)
server.set_endpoint("opc.tcp://localhost:4840/freeopcua/server/")

# 添加命名空间
uri = "http://examples.freeopcua.github.io"
idx = server.register_namespace(uri)

# 获取服务器对象节点
objects = server.get_objects_node()

# 在对象节点下添加一个新的对象
myobj = objects.add_object(idx, "MyObject")

# 添加变量到对象中
myvar = myobj.add_variable(idx, "MyVariable", 6.7)

# 设置变量为可写
myvar.set_writable()

# 启动服务器
server.start()

try:
    print("OPC UA 服务器已启动")
    while True:
        # 更新变量值
        time.sleep(1)
        new_value = myvar.get_value() + 0.1
        myvar.set_value(new_value)
except KeyboardInterrupt:
    print("关闭服务器")
    server.stop()

这个示例创建了一个OPC UA服务器,在端点opc.tcp://localhost:4840/freeopcua/server/上运行,并添加了一个命名空间和一个简单的变量节点。

以下是一个简单的OPC UA客户端示例,它连接到上述服务器并读取变量的值:

from opcua import Client

# 连接到服务器
client = Client("opc.tcp://localhost:4840/freeopcua/server/")

try:
    client.connect()
    
    # 获取对象节点
    root = client.get_root_node()
    print("Root node is: ", root)

    # 找到我们之前创建的变量节点
    obj = root.get_child(["0:Objects", "2:MyObject"])
    var = obj.get_child("2:MyVariable")
    
    # 读取变量值
    while True:
        value = var.get_value()
        print("Variable value: ", value)
        time.sleep(1)
finally:
    client.disconnect()

这个客户端连接到服务器,并循环读取变量MyVariable的值并打印出来。

通过WireShark抓包如下:

image

可以看到是一个非常典型的HELLO握手,安全认证,数据交互,关闭连接的过程,下面我将逐一解析这些报文:

Hello 报文结构解析

下面的表格将原始十六进制数据与 Wireshark 解析出的字段一一对应,并解释了每个字段的含义。

字节偏移 (原始数据) 字段名称 原始十六进制值 长度 (字节) 解析说明与值转换
消息头 (Message Header)
0-2 消息类型 (MessageType) 48 45 4C 3 对应 ASCII 字符:H E L,表示这是一个 HELlo 报文 。
3 保留位 (Chunk Type) 46 1 固定为字母 F 的 ASCII 码 。
4-7 消息总长度 (MessageSize) 4A 00 00 00 4 小端序 (Little-Endian) 字节序。4A 00 00 00 转换为十进制是 74 字节。这表示整个报文(8字节头+66字节体)的总长度 。
Hello 报文消息体
8-11 协议版本 (Version) 00 00 00 00 4 版本号为 0,表示使用初始版本的 OPC UA TCP 协议 。
12-15 接收缓冲区大小 (ReceiveBufferSize) FF FF FF 7F 4 小端序。FF FF FF 7F 转换为十进制是 2,147,483,647 字节(约 2GB)。这是客户端声明其能接收的单个数据块(Message Chunk)的最大大小 。
16-19 发送缓冲区大小 (SendBufferSize) FF FF FF 7F 4 小端序。值同样是 2,147,483,647 字节。这是客户端声明其能发送的单个数据块的最大大小 。
20-23 最大消息大小 (MaxMessageSize) 00 00 00 00 4 值为 0,表示客户端对服务器响应的完整消息(可能由多个块组成)总大小不设限制 。
24-27 最大分块数量 (MaxChunkCount) 00 00 00 00 4 值为 0,表示客户端对服务器响应消息被分割成的最大块数不设限制 。
28-31 端点URL长度 2A 00 00 00 4 小端序。2A 00 00 00 转换为十进制是 42。这表示接下来的端点 URL 字符串长度为 42 个字节。
32-73 端点URL (EndpointUrl) 6F 70 63... 42 ASCII 解码为:opc.tcp://localhost:4840/freeopcua/server/。这是客户端希望连接的服务器地址 。

这是OPC UA通信流程的第一步,主要用于协商通信参数,如缓冲区大小,确保双方能够高效、正确地处理后续数据 。服务器在收到Hello后,会回复一个Acknowledge(ACK)报文来确认这些参数。

ACK 报文解析

字节偏移 (原始数据) 字段名称 原始十六进制值 长度 (字节) 解析说明与值转换
消息头 (Message Header)
0-2 消息类型 (MessageType) 41 43 4B 3 对应 ASCII 字符:A C K,表示这是一个 ACKnowledge 报文,即服务器对客户端 Hello 报文的确认响应。
3 保留位 (Chunk Type) 46 1 固定为字母 F 的 ASCII 码,这与 HEL 报文中的规则一致。
4-7 消息总长度 (MessageSize) 1C 00 00 00 4 小端序。1C 00 00 00 转换为十进制是 28 字节。这与 Wireshark 解析出的"Message Size: 28"完全一致,表示整个 ACK 报文(8字节头 + 20字节体)的总长度。
ACK 报文消息体
8-11 协议版本 (Version) 00 00 00 00 4 版本号为 0,表示服务器同意使用(或支持)初始版本的 OPC UA TCP 协议。
12-15 接收缓冲区大小 (ReceiveBufferSize) FF FF FF 7F 4 小端序。FF FF FF 7F 转换为十进制是 2,147,483,647 字节(约 2GB)。此字段表示服务器能接收的最大消息块(Message Chunk)大小。它通常小于等于客户端在 Hello 报文中声明的 SendBufferSize。
16-19 发送缓冲区大小 (SendBufferSize) FF FF FF 7F 4 小端序。值同样为 2,147,483,647 字节。此字段表示服务器能发送的最大消息块大小。它通常小于等于客户端在 Hello 报文中声明的 ReceiveBufferSize。
20-23 最大消息大小 (MaxMessageSize) 00 00 00 00 4 值为 0,表示服务器对客户端后续发来的完整消息(可能由多个块组成)总大小不设限制。
24-27 最大分块数量 (MaxChunkCount) 00 00 00 00 4 值为 0,表示服务器对客户端消息被分割成的最大块数不设限制。

深入理解 ACK 报文

  • 核心作用ACK 报文完成了 OPC UA 连接的初始握手。服务器通过此报文确认客户端的连接参数,并告知客户端自己将使用的参数。双方将根据协商后的参数(主要是 ReceiveBufferSizeSendBufferSize)进行后续通信。
  • 参数协商:在您给出的例子中,客户端和服务器非常“豪爽”地将缓冲区大小设置为系统允许的最大值(约2GB),且不对消息和分块数做限制,这通常是为了追求最高的通信性能。
  • 后续流程:在 HEL/ACK 握手成功后,通信链路的基础参数就已确定。接下来,客户端会发送 OPN (OpenSecureChannel) 请求,开始建立安全通道,进行身份认证和密钥协商,为后续加密的业务数据通信做准备。

OPN (OpenSecureChannel) 请求报文解析

OPN (OpenSecureChannel) 报文是 OPC UA 连接建立过程中至关重要的一步,它在 HEL/ACK 握手之后,负责协商安全策略并建立用于加密通信的安全通道。

协议层/字段 原始十六进制值 (如提供) 长度 (字节) 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) 4f 50 4e 3 对应 ASCII 字符:O P N,表示这是一个 OPeN 安全通道的请求报文 。
分块类型 (Chunk Type) 46 1 值为 F (ASCII),表示这是该消息的最终分块(或唯一分块)。
消息总长度 (MessageSize) 84 00 00 00 4 小端序。84 00 00 00 转换为十进制是 132 字节。这与 Wireshark 显示一致 。
安全通道头 (SecureChannel Header)
安全通道ID (SecureChannelId) 00 00 00 00 4 值为 0。在初始的 OpenSecureChannel 请求中,此 ID 尚未分配,故为 0。服务器会在响应中分配一个有效的非零 ID 。
安全头 (Security Header)
安全策略URI长度 2f 00 00 00 4 小端序。2f 00 00 00 转换为十进制是 47。表示接下来的安全策略 URI 字符串长度为 47 个字节。
安全策略URI (SecurityPolicyUri) 68747470...4e6f6e65 47 ASCII 解码为:http://opcfoundation.org/UA/SecurityPolicy#None。这表明客户端和服务器协商使用无安全模式(即不加密、不签名),通常用于测试或内部安全网络 。
发送方证书长度 ff ff ff ff 4 值为 -1(补码表示)。这是一个特殊值,表示没有发送方证书(SenderCertificate 字段缺失),与 Wireshark 解析的 [OpcUa Null ByteString] 一致。这与选择的 None 安全策略相符 。
接收方证书指纹长度 ff ff ff ff 4 值同样为 -1。表示没有接收方证书指纹(ReceiverCertificateThumbprint 字段缺失),原因同上 。
序列头 (Sequence Header)
序列号 (SequenceNumber) 01 00 00 00 4 序列号为 1。这是一个单调递增的数字,用于防止重放攻击,确保消息的顺序性和新鲜度 。
请求ID (RequestId) 01 00 00 00 4 请求ID为 1。用于唯一标识这个请求,服务器在对应的 OpenSecureChannel 响应中会使用相同的 RequestId 。
消息体 (Message Body - OpenSecureChannelRequest) (后续字节) 可变 这部分包含建立安全通道所需的详细参数,例如:
客户端协议版本
请求类型(创建新通道或更新现有通道)
安全模式(与安全策略URI对应)
客户端非ce(一个随机数,用于密钥派生)
请求的生命周期(安全令牌的有效时间)
由于你提供的报文截断,未能展示完整消息体,但类型为 Encodeable Object

深入理解 OPN 报文

  • 核心作用OPN 报文标志着 OPC UA 通信从简单的连接参数协商进入到了安全上下文建立阶段。即使本例中使用了 SecurityPolicy#None(不加密),其建立的 SecureChannelId 和安全管理结构仍是后续所有应用层服务(如 CreateSession, Read, Write)的基础 。
  • 安全策略的意义:安全策略URI明确指出了通信将采用的安全级别。#None 表示无安全措施。在实际生产环境中,通常会使用如 #Basic256Sha256 等策略,以实现加密和签名,保证数据的机密性和完整性 。
  • 后续流程:服务器收到此请求后,会验证其有效性,然后发送 OPN 响应报文。在响应报文中,服务器会分配一个非零的 SecureChannelId 并返回其自身的非ce。双方据此可派生出对称密钥,用于后续 MSG 类型报文的加密和签名(在本例中因策略为 None 而跳过)。安全通道建立后,客户端才会发送 CreateSession 请求来创建应用会话。

MSG (Secure Message) 报文解析 - CreateSessionRequest

这是 OPC UA 连接建立过程中,在安全通道(由之前的 OPN 消息建立)之上创建应用会话的关键一步。这条 MSG 类型的报文承载了一个 CreateSessionRequest 服务请求。我们来详细解析一下。

协议层/字段 原始十六进制值 (如提供) 长度 (字节) 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) 4d 53 47 3 对应 ASCII 字符:M S G。这表明该消息是在已建立的安全通道内传输的、受保护的应用层消息 。
分块类型 (Chunk Type) 46 1 值为 F (ASCII),表示这是该消息的最终分块(或唯一分块)。
消息总长度 (MessageSize) 1f 01 00 00 4 小端序。1f 01 00 00 转换为十进制是 287 字节。这与 Wireshark 显示一致。
安全通道头 (SecureChannel Header)
安全通道ID (SecureChannelId) 07 00 00 00 4 值为 7。这是在之前的 OpenSecureChannel 响应中由服务器分配的有效通道ID,用于标识当前连接 。
安全令牌头 (Security Token Header)
安全令牌ID (Security Token Id) 0d 00 00 00 4 值为 13。用于标识用于保护此消息的特定安全令牌集(对称密钥)。
序列头 (Sequence Header)
序列号 (SequenceNumber) 02 00 00 00 4 序列号为 2。这是一个在安全通道上单调递增的数字,用于防止重放攻击。
请求ID (RequestId) 02 00 00 00 4 请求ID为 2。用于唯一标识这个应用层的服务请求,服务器在对应的 CreateSession 响应中会使用相同的 RequestId。
消息体 (Message Body) - CreateSessionRequest
类型标识符 (TypeId) 01 00 cd 01 4 标识后续编码对象的类型。cd 01(小端序为 0x000001cd,即461)代表 CreateSessionRequest 服务 。
请求头 (RequestHeader) 所有服务请求的通用头信息。
AuthenticationToken 00 00 2 认证令牌,此时会话尚未建立,故为0(空NodeId)。
Timestamp (后续字节) 8 请求发起的时间戳。
RequestHandle 02 00 00 00 4 请求句柄,值为 2。由客户端分配,用于在客户端内部跟踪此请求。
ReturnDiagnostics 00 00 00 00 4 请求服务器返回的诊断信息级别,0表示不返回额外诊断信息。
TimeoutHint e8 03 00 00 4 超时提示,单位为毫秒。e8 03 00 00 转换为十进制是 1000 毫秒(1秒),指示服务器应在此时间内完成处理。
ClientDescription 客户端的应用程序描述信息 。
ApplicationUri 75 72 6e 3a... 20 字符串,解码为 urn:freeopcua:client。客户端的唯一标识符。
ProductUri 75 72 6e 3a... 30 字符串,解码为 urn:freeopcua.github.io:client。客户端产品的URI。
ApplicationName 50 79 74 68... 18 本地化文本,解码为 Pure Python Client。客户端应用的可读名称。
ApplicationType 01 00 00 00 4 应用类型为 1,表示这是一个 Client 应用。
EndpointUrl 6f 70 63 2e... 42 字符串,解码为 opc.tcp://localhost:4840/freeopcua/server/。客户端希望连接的服务器端点URL。
SessionName 50 75 72 65... 27 字符串,解码为 Pure Python Client Session1。客户端为此次会话提议的名称。
ClientNonce 7b 4e 88 e7... 32 一个由客户端生成的 32字节随机数。用于会话级别的安全验证,例如在 ActivateSession 过程中证明客户端是会话的合法创建者 。
ClientCertificate ff ff ff ff 4 值为 -1,表示没有提供客户端证书([OpcUa Null ByteString])。这与当前连接可能使用的安全策略(如 None)相符。
RequestedSessionTimeout 00 40 77 4b 41 00 00 00 8 请求的会话超时时间。00 40 77 4b 41 00 00 00 可能表示一个较大的值,此处可能与超时时间关联,但具体数值需结合上下文确认,通常以毫秒为单位。

深入理解 CreateSession 请求

  • 核心作用CreateSession 请求是在安全通道 (SecureChannel) 成功建立后,创建应用层会话的第一步。这个会话将管理更高级别的状态,如用户凭据、订阅等 。它不同于传输层的安全通道。
  • 安全上下文的演进MSG 类型报文表明通信已进入安全通道保护之下。报文中出现的 SecureChannelIdSecurity Token Id 都证明了这一点。应用层会话的建立(CreateSession/ActivateSession)进一步增强了安全性,特别是引入了用户身份认证 。
  • 关键字段解读
    • ClientNonce:这是一个重要的安全要素,用于在后续的 ActivateSession 中验证客户端的合法性,防止重放攻击 。
    • SessionName:为会话提供一个可读的标识,便于在服务器端进行管理和诊断。
    • RequestedSessionTimeout:客户端希望服务器保持此会话活跃的最长时间(以毫秒为单位)。如果在此时间内没有通信活动,服务器可能会关闭会话 。
  • 后续流程:服务器收到此请求后,会验证其有效性并创建会话上下文,然后回复一个 CreateSessionResponse 报文。该响应报文将包含服务器分配的正式 AuthenticationToken(用于标识此会话)、服务器生成的 ServerNonce 以及其他会话参数。之后,客户端需要发送 ActivateSession 请求来最终激活并使用这个会话。

MSG (Secure Message) 报文解析 - ActivateSessionRequest

下面是对这条 ActivateSessionRequest 报文的详细解析。

这是 OPC UA 会话建立过程中至关重要的一步。ActivateSessionRequest 用于激活一个已创建的会话,并在此过程中完成用户身份认证。这意味着通信即将从“系统层面”的安全通道建立,进入到“用户层面”的权限控制。

协议层/字段 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) MSG (4d 53 47)。这表明这是在安全通道内传输的应用消息。
分块类型 (Chunk Type) F (46),表示这是该消息的最终或唯一分块。
消息总长度 (MessageSize) 9c 00 00 00 (小端序),转换为十进制是 156 字节。
安全通道ID (SecureChannelId) 07 00 00 00,值为 7。与此前建立的 Secure Channel 对应 。
安全令牌ID (Security Token Id) 0d 00 00 00,值为 13。标识用于保护此消息的特定安全令牌。
序列号 (SequenceNumber) 03 00 00 00,值为 3。在安全通道上单调递增,用于防重放。
请求ID (RequestId) 03 00 00 00,值为 3。用于匹配请求与响应。
消息体 - ActivateSessionRequest
类型标识符 (TypeId) d3 01 (小端序为 0x000001d3,即467),代表 ActivateSessionRequest 服务。
请求头 (RequestHeader)
认证令牌 (AuthenticationToken) 02 00 ea 03。这是一个 NodeId,其中命名空间索引为0,标识符数值为 1002这是关键变化:它不再是空值或0,而是服务器在 CreateSessionResponse 中分配的唯一令牌,用于明确标识此会话
时间戳 (Timestamp) 请求发起的具体时间。
请求句柄 (RequestHandle) 03 00 00 00,值为 3。由客户端分配,用于内部跟踪。
超时提示 (TimeoutHint) e8 03 00 00,即 1000 毫秒(1秒)。
客户端签名 (ClientSignature) 算法为 http://www.w3.org/2000/09/xmldsig#rsa-sha1,但签名为空。这可能与安全策略设置为 None 有关 。
用户身份令牌 (UserIdentityToken) 这是本次请求的核心,用于用户认证 。
令牌类型 (TypeId) 41 01 (小端序 0x00000141,即321),代表 AnonymousIdentityToken(匿名身份令牌)。
策略ID (PolicyId) 字符串 anonymous。这表明客户端选择以匿名方式激活会话。服务器若支持此方式,将不会进行用户级认证。
区域设置ID (LocaleIds) 包含一个字符串 en。客户端用它向服务器表明希望接收文本信息(如诊断信息)时使用的语言。

深入理解 ActivateSession 请求

  • 核心作用ActivateSession 是会话生命周期中的“启动”步骤。它将之前 CreateSession 创建的会话上下文从“就绪”状态变为“活跃”状态,允许开始真正的数据交换(如读取、写入、订阅)。此过程也完成了最终的用户身份验证和授权 。
  • 匿名认证分析:您提供的报文中使用了 AnonymousIdentityToken。这是一种最简单的认证方式,意味着客户端不提供任何用户名、密码或证书。这通常用于测试环境或完全信任的内部网络。在生产环境中,为了安全,应使用更强大的认证方式,如用户名/密码或证书 。
  • 认证令牌的意义AuthenticationToken 字段此时携带的值(1002)至关重要。它像是在告诉服务器:“我要激活的是之前我们商量好的那个会话(ID为1002)”。服务器会验证此令牌是否有效且属于当前连接,从而将用户权限(本例为匿名用户)与会话绑定。
  • 安全上下文的延续:尽管本次激活使用了匿名认证,但整个 MSG 报文仍然在 SecureChannelId=7 的安全通道内传输,受到通道层面安全设置的保护 。

后续流程

服务器收到此请求后,将验证其有效性(如检查令牌、确认支持匿名登录等)。如果一切正常,服务器会回复一个 ActivateSessionResponse 报文。该响应会包含一个服务器生成的 Nonce(用于后续重要操作的签名验证)以及操作结果。

一旦 ActivateSession 成功,会话就完全建立并处于活动状态了。 接下来,客户端就可以发送诸如 Browse(浏览地址空间)、Read(读取数据)、CreateSubscription(创建订阅)等业务操作请求了。

希望这份详细的解析能帮助你清晰地理解 ActivateSessionRequest 的构成和作用!如果你有服务器的响应报文或其他类型的报文需要分析,我们可以继续。

MSG (Secure Message) 报文解析 - TranslateBrowsePathsToNodeIdsRequest

这是 OPC UA 中一个非常实用且关键的请求,它用于根据浏览路径(BrowsePath) 来解析出具体的节点ID(NodeId)。这对于在复杂的地址空间中动态定位节点非常有用。

下面是对这条 TranslateBrowsePathsToNodeIdsRequest 报文的详细解析。

报文头与请求基础

协议层/字段 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) MSG (4d 53 47)。安全通道内的应用消息。
分块类型 (Chunk Type) F (46),最终分块。
消息总长度 (MessageSize) 6b 00 00 00 (小端序),转换为十进制是 107 字节。
安全通道ID (SecureChannelId) 07 00 00 00,值为 7。使用已建立的安全通道。
安全令牌ID (Security Token Id) 0d 00 00 00,值为 13
序列号 (SequenceNumber) 04 00 00 00,值为 4
请求ID (RequestId) 04 00 00 00,值为 4
消息体 - TranslateBrowsePathsToNodeIdsRequest
类型标识符 (TypeId) 2a 02 (小端序为 0x0000022a,即554),代表 TranslateBrowsePathsToNodeIdsRequest 服务 。
请求头 (RequestHeader)
认证令牌 (AuthenticationToken) 02 00 ea 03。NodeId,命名空间索引0,标识符数值为 1002。这表明请求在已激活的会话上下文中。
请求句柄 (RequestHandle) 04 00 00 00,值为 4
超时提示 (TimeoutHint) e8 03 00 00,即 1000 毫秒。

核心:浏览路径解析

这是本次请求的精华所在。客户端提供了一个浏览路径,希望服务器将其转换为确切的节点ID。

字段 原始十六进制值 (如提供) 解析说明与值转换
BrowsePaths 数组大小 01 00 00 00 本次请求只包含 1 条浏览路径。
起始节点 (StartingNode) 54 00 00 00 节点ID。编码表明是一个数字类型的NodeId,其值为 840x54)。这通常是地址空间中的一个已知节点,比如 ObjectsFolder 的节点ID可能就是84。
相对路径 (RelativePath) 路径由两个元素(RelativePathElement)组成 。
第1个路径元素
引用类型ID (ReferenceTypeId) 21 00 00 00 值为 330x21)。这通常对应 HierarchicalReferences 引用类型,表示一种层次化的包含关系 。
是否反向 (IsInverse) False 沿引用方向浏览。
是否包含子类型 (IncludeSubtypes) True 包含所有子类型的引用。
目标名称 (TargetName) 00 00 07 00 00 00 4f 62 6a 65 63 74 73 QualifiedName。命名空间索引为 0,名称为 "Objects"。含义:从起始节点出发,沿着 HierarchicalReferences 引用,寻找 BrowseName 为 "Objects" 的子节点。
第2个路径元素
引用类型ID (ReferenceTypeId) 21 00 00 00 同样为 33 (HierarchicalReferences)。
是否反向 (IsInverse) False 沿引用方向浏览。
是否包含子类型 (IncludeSubtypes) True 包含所有子类型的引用。
目标名称 (TargetName) 02 00 08 00 00 00 4d 79 4f 62 6a 65 63 74 QualifiedName。命名空间索引为 2,名称为 "MyObject"。含义:从上一个元素找到的节点出发,继续沿着 HierarchicalReferences 引用,寻找 BrowseName 为 "MyObject" 且位于命名空间2下的节点。

路径解析示例

假设服务器的地址空间结构如下:

Root
└── Objects (NodeId=ns=0;i=85) // 这是起始节点(84)的一个子节点
    └── MyObject (NodeId=ns=2;s=MyObject_UniqueIdentifier) // 这是目标节点

这个 TranslateBrowsePathsToNodeIdsRequest 所做的就是:
“请从节点ID为84的节点开始,先找到其直接子节点中BrowseName为'Objects'的节点,然后再从找到的这个节点出发,找到其直接子节点中BrowseName为'MyObject'且命名空间索引为2的节点,并返回该节点的NodeId。”

深入理解与服务价值

  • 核心作用TranslateBrowsePathsToNodeIds 服务是OPC UA动态导航的利器。当客户端不知道一个节点的精确NodeId,但知道它在地址空间中的逻辑路径时,这个服务就变得极其有用 。它允许基于有意义的名称(BrowseName)进行导航,而不是硬编码难以理解的NodeId。
  • 与Browse的区别Browse 服务是查看一个节点的邻居,而 TranslateBrowsePathsToNodeIds 是沿着一条预设的路径“走”到一个特定的节点。前者是“探索”,后者是“定位” 。
  • 安全提示:值得注意的是,过于复杂或恶意的浏览路径解析可能对服务器构成压力。历史上,在OPC UA .NET Standard Stack的早期版本中,就曾存在一个与TranslateBrowsePathsToNodeIds服务相关的栈溢出拒绝服务漏洞(CVE-2022-29866),其修复方式正是加强对浏览路径的校验 。

后续响应

服务器在收到此请求后,会执行路径解析。如果路径上的每个节点都存在,服务器将回复一个 TranslateBrowsePathsToNodeIdsResponse,其中包含一个 BrowsePathResult 数组,会为每条路径返回解析出的目标节点ID(以及路径中最后一步的有效剩余路径,如果存在)。如果路径无效,则会返回相应的错误码。

ReadRequest

这是 OPC UA 通信中最核心、最常用的数据交互服务之一。ReadRequest 用于从服务器的一个或多个节点读取属性值(最常用的是 Value 属性)。下面是对这条报文的详细解析。

协议层/字段 原始十六进制值 (如提供) 长度 (字节) 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) 4d 53 47 3 对应 ASCII 字符:M S G。这是在已建立的安全通道和会话内传输的应用层消息。
分块类型 (Chunk Type) 46 1 值为 F (ASCII),表示这是该消息的最终分块(或唯一分块)。
消息总长度 (MessageSize) 63 00 00 00 4 小端序。转换为十进制是 99 字节。
安全通道ID (SecureChannelId) 07 00 00 00 4 值为 7。标识当前使用的安全通道。
安全令牌ID (Security Token Id) 0d 00 00 00 4 值为 13。标识用于保护此消息的安全令牌。
序列头 (Sequence Header)
序列号 (SequenceNumber) 06 00 00 00 4 序列号为 6。在安全通道上单调递增,用于防重放攻击。
请求ID (RequestId) 06 00 00 00 4 请求ID为 6。用于唯一标识这个服务请求,服务器在对应的 ReadResponse 中会使用相同的 RequestId。
消息体 - ReadRequest
类型标识符 (TypeId) 77 02 2 (小端序) 小端序为 0x00000277,即631,代表 ReadRequest 服务。
请求头 (RequestHeader)
认证令牌 (AuthenticationToken) 02 00 ea 03 变长 NodeId,命名空间索引0,标识符数值为 1002。这是服务器在 CreateSession 响应中分配的令牌,用于标识当前活跃的应用会话
时间戳 (Timestamp) f2 1c 9d 45... 8 请求发起的具体时间。
请求句柄 (RequestHandle) 06 00 00 00 4 值为 6。由客户端分配,用于内部跟踪此请求。
超时提示 (TimeoutHint) e8 03 00 00 4 1000 毫秒(1秒)。提示服务器应在此时间内处理完请求。
Read 服务参数
MaxAge 00 00 00 00 8 值为 0 毫秒。表示客户端希望读取最新的数据,而不接受服务器端的任何缓存值。
TimestampsToReturn 00 00 00 00 4 值为 0,表示 Source 时间戳。指示服务器在响应中应返回哪个时间戳(源时间戳、服务器时间戳或两者都返回)。
NodesToRead 数组
数组大小 01 00 00 00 4 值为 1。表示本次请求只读取1个节点。
[0]: ReadValueId (第一个要读取的节点)
节点ID (NodeId) 02 00 02 00 00 00 变长 这是关键字段。这是一个数字类型的NodeId:
• 命名空间索引 (Namespace Index): 2
• 标识符类型 (Identifier Type): 数值 (Numeric)
• 标识符 (Identifier): 2
因此,完整的节点ID是 ns=2;i=2。这是客户端要读取的目标节点在服务器地址空间中的唯一标识。
属性ID (AttributeId) 0d 00 00 00 4 值为 13 (0xD)。这表示要读取的是节点的 Value 属性,即变量的当前值。
索引范围 (IndexRange) ff ff ff ff 4 空值(-1)。表示读取整个值,而不是数组的某个索引或范围。
数据编码 (DataEncoding) 00 00 ff ff ff ff 变长 空值。表示使用默认的数据编码方式。

深入理解 ReadRequest 报文

  • 核心作用Read 服务是 OPC UA 中最基本的数据获取方式。客户端通过指定一个或多个节点的 NodeIdAttributeId,可以高效地从服务器获取实时数据。这是监控设备状态、传感器读数等场景下最频繁的操作。
  • 会话上下文:请注意 AuthenticationToken 字段(值=1002)。这表明该 ReadRequest 是在一个已成功创建并激活的会话上下文中发出的。服务器会验证此令牌的有效性,从而将请求与特定的用户会话和权限关联起来。
  • 关键参数解读
    • MaxAge=0:这是一个重要设置,意味着客户端要求“尽可能最新”的数据。如果设置一个正数(如60000毫秒),服务器可能会返回1分钟内的缓存值(如果存在且未过期),这有助于减少服务器负载和网络流量,适用于对实时性要求不极高的场景。
    • 节点 ns=2;i=2:在典型的 OPC UA 服务器地址空间中,命名空间2(ns=2)通常用于存放用户自定义或设备特定的变量。读取此节点很可能是为了获取某个关键工艺参数或设备状态。
  • 性能与可靠性Read 服务支持批量读取,即一次性请求中读取多个节点,如 NodesToRead 数组大小可以大于1。这能显著减少网络往返次数,提高通信效率。在实际开发中,正确处理返回的 Results 数组顺序与请求中的 NodesToRead 数组顺序的对应关系至关重要。

ReadResponse

这是对之前 ReadRequest 的服务器响应,它携带了所请求节点的数据值。下面是对这条 ReadResponse 报文的详细解析。

协议层/字段 原始十六进制值 (如提供) 长度 (字节) 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) 4d 53 47 3 对应 ASCII 字符:M S G。安全通道内的应用消息响应。
分块类型 (Chunk Type) 46 1 值为 F (ASCII),表示这是该消息的最终分块
消息总长度 (MessageSize) 52 00 00 00 4 小端序。转换为十进制是 82 字节。
安全通道ID (SecureChannelId) 07 00 00 00 4 值为 7。与请求使用相同的安全通道。
安全令牌ID (Security Token Id) 0d 00 00 00 4 值为 13
序列头 (Sequence Header)
序列号 (SequenceNumber) 06 00 00 00 4 序列号为 6此值与对应的 ReadRequest 的序列号相同,用于匹配请求与响应 。
请求ID (RequestId) 06 00 00 00 4 请求ID为 6此值与对应的 ReadRequest 的 RequestId (6) 完全相同,这是客户端将响应与特定请求关联起来的关键 。
消息体 - ReadResponse
类型标识符 (TypeId) 7a 02 2 (小端序) 小端序为 0x0000027a,即634,代表 ReadResponse 服务。
响应头 (ResponseHeader)
时间戳 (Timestamp) 86 24 9d 45... 8 服务器发送此响应的时间戳。
请求句柄 (RequestHandle) 06 00 00 00 4 值为 6从对应的 ReadRequest 中复制而来,帮助客户端在应用层进行内部匹配。
服务结果 (ServiceResult) 00 00 00 00 4 状态码为 0x00000000,即 Good,表示整个读取操作成功执行。
Results 数组 这是响应的核心内容
数组大小 01 00 00 00 4 值为 1。表示结果数组包含1个 DataValue,与请求中的 NodesToRead 数组大小(1)严格对应 。
[0]: DataValue (第一个节点的结果)
编码掩码 (EncodingMask) 07 1 值为 0x07(二进制 00000111)。这表明此 DataValue 包含:
Value (位0:1)
StatusCode (位1:1)
SourceTimestamp (位2:1)
• 不包含 ServerTimestamp 等(位3:0)。
状态码 (StatusCode) 00 00 00 00 4 值为 Good。表示读取这个特定节点的值时没有发生错误。
值 (Value) - Variant 类型
Variant 类型 0b 1 值为 0x0b,表示 Variant 中包装的数据类型是 Double (双精度浮点数)。
Double 值 f7 ff ff ff ff 26 40 8 根据 IEEE 754 标准编码的双精度浮点数。解码后的值为 11.499999999999984。这就是客户端请求读取的节点 ns=2;i=2 的当前值。
源时间戳 (SourceTimestamp) 9c 51 3c 45 00 53 dc 01 8 数据源(例如,传感器或PLC)产生该数值的时间戳。因为请求中的 TimestampsToReturn 设置为 Source

深入理解 ReadResponse 报文

  • 核心作用ReadResponse 是 OPC UA 数据交换中最基本的成功响应。它准确地将请求的结果返回给客户端。Results 数组中的顺序与请求中 NodesToRead 数组的顺序完全一致,这是正确解析数据的关键 。
  • 状态码的双重性:注意响应中有两个状态码:
    1. ResponseHeader.ServiceResult:表示整个服务请求的执行情况(如“服务器是否理解并处理了该请求?”)。
    2. DataValue.StatusCode:表示读取特定节点值的操作结果(如“该节点的值是否可读?是否质量良好?”)。
      本例中两者均为 Good,说明请求完全成功。
  • 数据价值与时间戳:返回的不仅仅是一个数值(11.5),还有一个重要的源时间戳。这提供了数据的“上下文”,客户端可以知道这个值是在何时产生的,对于数据的历史追踪、趋势分析及报警判断至关重要。
  • 错误处理:如果读取某个节点失败(例如,节点不存在或权限不足),ServiceResult 可能仍然是 Good(表示请求已被处理),但失败的节点对应的 DataValue.StatusCode 将设置为特定的错误码(如 Bad_NodeIdUnknown)。客户端必须检查每个结果的状态码 。

这条 ReadResponse 报文表明:

  • 请求成功:服务器成功处理了 RequestId=6 的读取请求。
  • 数据有效:所请求的节点 (ns=2;i=2) 的当前值是 11.499999999999984 (一个 Double 类型的值),该值于 2025-11-11 19:42:34.095862 在数据源处产生。
  • 通信状态良好:安全通道 (ID=7) 和会话正常工作。

至此,一个完整的 OPC UA 读取操作(请求-响应)周期就结束了。

CloseSessionRequest 报文解析

这是 OPC UA 会话生命周期中的最后一步。客户端发送 CloseSessionRequest 来关闭一个活跃的应用会话,并释放相关资源。服务器在成功处理此请求后,会回复 CloseSessionResponse,但不会等待客户端确认(即无进一步应答),会话即告终止。

协议层/字段 原始十六进制值 (对应字节偏移) 长度 (字节) 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) 4d 53 47 (0-2) 3 对应 ASCII 字符:M S G。表示这是在安全通道内传输的应用层消息。
分块类型 (Chunk Type) 46 (3) 1 值为 F (ASCII),表示这是该消息的最终分块(或唯一分块)。
消息总长度 (MessageSize) 3f 00 00 00 (4-7) 4 小端序。转换为十进制是 63 字节。指整个报文(头+体)的总长度。
安全通道头 (SecureChannel Header)
安全通道ID (SecureChannelId) 07 00 00 00 (8-11) 4 值为 7。标识当前正在使用的安全通道。
安全令牌头 (Security Token Header)
安全令牌ID (Security Token Id) 0d 00 00 00 (12-15) 4 值为 13。标识用于保护此消息的安全令牌。
序列头 (Sequence Header)
序列号 (SequenceNumber) 0f 00 00 00 (16-19) 4 序列号为 15。在安全通道上单调递增,用于防重放攻击。
请求ID (RequestId) 0f 00 00 00 (20-23) 4 请求ID为 15。用于唯一标识这个服务请求。
消息体 - CloseSessionRequest
类型标识符 (TypeId) 01 00 d9 01 (24-27) 4 编码为 ExpandedNodeId。其中标识符为小端序 0x000001d9,即十进制 473,代表 CloseSessionRequest 服务。
请求头 (RequestHeader)
认证令牌 (AuthenticationToken) 02 00 ea 03 (28-31) 变长 这是一个 NodeId。编码掩码 0x02 表示数值型标识符,命名空间索引为 0,标识符数值为 1002ea 03 小端序)。这是服务器在 CreateSession 响应中分配的会话令牌,用于明确指定要关闭哪个会话。
时间戳 (Timestamp) 98 36 00 4b 00 53 dc 01 (32-39) 8 请求发起的具体时间戳。
请求句柄 (RequestHandle) 0f 00 00 00 (40-43) 4 值为 15。由客户端分配,用于内部跟踪此请求。
超时提示 (TimeoutHint) e8 03 00 00 (48-51) 4 值为 1000 毫秒(1秒)。提示服务器应在此时间内处理请求。
DeleteSubscriptions 01 (63) 1 值为 True (0x01)。这是一个重要的布尔标志,指示服务器在关闭会话的同时,自动删除与该会话关联的所有订阅(Subscription)和监控项(MonitoredItem)。这可以防止服务器端残留无效的订阅资源。

深入理解 CloseSession 请求

  • 核心作用CloseSession 是一个优雅的会话终止程序。它允许客户端主动通知服务器“我将不再使用这个会话”,使服务器能及时清理与会话相关的所有资源(如安全令牌、订阅监控等)。这是一种良好的通信实践,有助于维护服务器端的健康状态 。
  • DeleteSubscriptions 标志:此字段设置为 True 是最常见和推荐的做法。它确保会话关闭时,所有相关的订阅和监控项也被清除,避免服务器持续为已断开的客户端浪费资源进行数据监控和通知排队 。
  • 无响应确认的响应:服务器在成功处理 CloseSessionRequest 后,会发送一个 CloseSessionResponse 作为确认。但值得注意的是,此后服务器会立即释放与会话相关的所有资源,并认为会话已结束。 因此,客户端在收到响应后,不应再使用该会话的 AuthenticationToken 发送任何请求 。
  • 通信流程上下文:这是 OPC UA 标准断开流程的一部分。完整的顺序通常是:
    1. 客户端发送 CloseSessionRequest 关闭应用会话。
    2. 服务器回复 CloseSessionResponse
    3. 客户端发送 CloseSecureChannelRequest 关闭底层安全通道。
    4. 服务器关闭网络连接(TCP Socket)。

这条 CloseSessionRequest 报文表明,客户端正在请求正常终止会话 ID 为 1002 的应用会话,并明确要求服务器同时清理所有关联的订阅。这是 OPC UA 通信结束时一个标准且必要的步骤,确保了资源管理的整洁性。

这标志着 OPC UA 通信链路的最终阶段。下面是对这条 CloseSecureChannelRequest 报文的详细解析。

CloseSecureChannelRequest

报文头与安全通道头

协议层/字段 原始十六进制值 长度 (字节) 解析说明与值转换
消息头 (Message Header)
消息类型 (MessageType) 43 4c 4f 3 对应 ASCII 字符:C L O,表示这是一个 CLOseSecureChannel 请求报文 。
分块类型 (Chunk Type) 46 1 值为 F (ASCII),表示这是该消息的最终分块(或唯一分块)。
消息总长度 (MessageSize) 3e 00 00 00 4 小端序。转换为十进制是 62 字节。指整个报文(头+体)的总长度。
安全通道头 (SecureChannel Header)
安全通道ID (SecureChannelId) 07 00 00 00 4 值为 7。标识当前要关闭的安全通道。此ID由服务器在最初的 OpenSecureChannel 响应中分配 。
安全令牌头 (Security Token Header)
安全令牌ID (Security Token Id) 0d 00 00 00 4 值为 13。标识用于保护此消息的安全令牌。

序列头与消息体

协议层/字段 原始十六进制值 长度 (字节) 解析说明与值转换
序列头 (Sequence Header)
序列号 (SequenceNumber) 10 00 00 00 4 序列号为 16。在安全通道上单调递增,用于防重放攻击。
请求ID (RequestId) 10 00 00 00 4 请求ID为 16。用于唯一标识这个服务请求。
消息体 - CloseSecureChannelRequest
类型标识符 (TypeId) 01 00 c4 01 4 小端序为 0x000001c4,即十进制 452,代表 CloseSecureChannelRequest 服务 。
请求头 (RequestHeader)
认证令牌 (AuthenticationToken) 02 00 ea 03 变长 NodeId,命名空间索引0,标识符数值为 1002。这是应用会话的认证令牌。请注意,此请求是在会话已关闭(CloseSession)后,于尚存的安全通道上发送的。
请求句柄 (RequestHandle) 10 00 00 00 4 值为 16。由客户端分配,用于内部跟踪。
超时提示 (TimeoutHint) e8 03 00 00 4 值为 1000 毫秒(1秒)。提示服务器应在此时间内处理请求。

深入理解与关键区别

这条报文是连接终止流程的最后一步,有几点需要特别注意:

  • 核心作用CloseSecureChannel 请求用于永久终止OpenSecureChannel 建立的安全通道。服务器收到后,会释放与该通道相关的所有资源,包括加密密钥和上下文信息 。
  • 无响应确认:与大多数 OPC UA 服务不同,服务器在成功处理 CloseSecureChannelRequest 后,不会发送任何响应报文 。客户端发送此请求后,即可认为安全通道已失效并主动关闭底层的 TCP 连接。
  • 正确的断开流程:一个优雅的 OPC UA 连接终止顺序是 :
    1. 客户端发送 CloseSessionRequest → 服务器回复 CloseSessionResponse应用会话结束
    2. 客户端发送 CloseSecureChannelRequest → 服务器释放资源,不回复安全通道结束
    3. 客户端/服务器关闭 TCP Socket (传输连接结束
  • 安全上下文:即使是在关闭请求中,报文仍然通过安全通道(ID=7)发送并受到相应安全策略的保护,这确保了关闭操作本身的安全性 。

这条 CloseSecureChannelRequest (CLO) 报文是 OPC UA 通信流程的“结束信号”。它指示服务器关闭 ID 为 7 的安全通道。由于服务器不对此请求进行响应,它的发出通常意味着整个 OPC UA 通信会话(从 HEL/ACK 握手到安全通道和应用会话的建立与使用)的终结。

posted @ 2025-11-11 20:39  Asp1rant  阅读(79)  评论(0)    收藏  举报