OPC UA(OPC Unified Architecture)是OPC基金会推出的下一代工业通信统一架构,旨在解决经典OPC基于COM/DCOM带来的跨平台限制、安全薄弱、配置复杂等问题。OPC UA不再是单纯的协议,而是一套包含信息建模、地址空间、订阅机制、安全体系的完整规范。本文结合赢式科技团队16年工业通讯项目实践,系统讲解OPC UA的信息模型、地址空间、订阅通知机制、安全策略,并给出C#上位机客户端开发示例,帮助开发者快速上手OPC UA。

一、OPC UA概述:与经典OPC的区别与核心优势

经典OPC(OPC DA/UA/HDA/AE)基于微软COM/DCOM技术,只能在Windows平台运行,且依赖DCOM配置,跨网段、跨平台场景几乎无法部署。2008年OPC基金会启动UA重构,2009年发布1.0版本,至今已发展到1.05版本,并在工业4.0、智能制造领域成为标准化的数据交换基础。

对比维度经典OPC (DA/AE/HDA)OPC UA
底层技术COM/DCOM独立服务,TCP/HTTP/WebSocket
跨平台仅WindowsWindows/Linux/嵌入式
数据建模扁平Tag点位对象化信息模型,含语义
安全机制DCOM鉴权,配置复杂证书+用户名+消息加密
访问方式仅同步读写读/写/订阅/历史/事件/方法调用
传输层RPCUA-TCP、HTTPS、WebSocket

OPC UA的三大核心优势

  • 平台无关:协议规范独立于操作系统与编程语言,C/C++/Java/C#/Python均有官方或开源SDK实现,西门子、三菱、罗克韦尔、倍福等主流PLC原生支持。
  • 安全可信:内置X.509证书认证、消息加密(AES-128/256)、签名(SHA-1/SHA-256)、用户令牌验证,符合工业信息安全要求。
  • 语义化信息模型:不再是"地址=数据"的扁平结构,而是用对象、属性、方法构建可被程序理解的设备模型,支持配套规范(如PLCopen、OPC UA for Machinery、FDI)。

二、OPC UA信息模型:节点与引用

OPC UA的信息模型是地址空间的基础。整个模型由节点(Node)引用(Reference)构成:节点表示数据、对象、方法等实体,引用表示节点之间的有向关系。这种"节点+引用"的图结构使得OPC UA既能表达简单的Tag点位,也能建模复杂的设备拓扑。

2.1 节点的核心属性

每个节点都拥有一组基础属性(Attributes),不同的节点类(NodeClass)还有各自专属属性。通用属性包括:

属性说明示例
NodeId节点唯一标识ns=2;s=MyDevice.Temperature
NodeClass节点类(Object/Variable/Method等)Variable
BrowseName浏览名(命名空间限定)2:Temperature
DisplayName显示名称当前温度
Description节点描述反应釜内温度传感器读数
WriteMask写权限掩码0x01

2.2 七种节点类型

OPC UA定义了7种NodeClass,开发者最常打交道的有Object、Variable、Method三类:

  • Object(对象节点):表示物理或逻辑实体,如设备、模块、文件夹。可包含子节点,本身不携带数据值。
  • Variable(变量节点):表示带值的数据点,如温度、压力、产量。有Value、DataType、EURange(量程)等专属属性。
  • Method(方法节点):表示可被客户端调用的方法,类似RPC调用,如Start、Stop、Reset。
  • ObjectType / VariableType:类型节点,定义对象/变量的"模板",支持继承。
  • DataType:数据类型节点,内置类型(Int32、Double、String等)和自定义结构体。
  • ReferenceType:引用类型节点,定义节点间关系类型,如Organizes、HasComponent、HasProperty。
  • View:视图节点,对地址空间的子集进行分组,便于按需浏览。

2.3 命名空间(Namespace)

为了避免不同厂商自定义节点名冲突,OPC UA引入命名空间机制。每个NodeId由命名空间索引(NamespaceIndex)+ 标识符组成。命名空间0是OPC UA标准命名空间,存放所有标准类型定义;命名空间1通常是服务器自身;命名空间2及以上是厂商扩展或配套规范(如PLCopen、MachineVision)。

// NodeId的三种标识符格式
ns=0;i=2258          // 命名空间0,数字标识符(标准节点:Server_ServerStatus_CurrentTime)
ns=2;s=Device.Temp   // 命名空间2,字符串标识符(厂商自定义)
ns=3;b=AA==          // 命名空间3,GUID/ByteString标识符

// BrowseName格式:命名空间索引:名称
2:Temperature
0:HasComponent

命名空间表通过GetNamespaces服务获取,客户端在解析NodeId前必须先获取服务器命名空间表,否则可能误把不同厂商的同名字符串节点理解为同一节点。

三、地址空间与浏览机制

OPC UA服务器把所有节点组织成一张可寻址的图,称为地址空间(AddressSpace)。客户端通过Browse操作从根节点(RootFolder)出发,沿引用关系遍历整张图,类似文件系统的目录浏览,但比文件系统灵活——一个节点可以有多个父节点(多对多引用)。

3.1 地址空间的层次结构

OPC UA标准定义了从根节点出发的几个标准入口:

Root (RootFolder)
├── Objects           // 客户端最常访问的入口,厂商实例化设备对象
│   ├── DeviceA
│   │   ├── Temperature (Variable)
│   │   ├── Pressure (Variable)
│   │   └── Start (Method)
│   └── DeviceB
├── Server            // 服务器自身信息,如ServerStatus、NamespaceArray
├── Types             // 类型定义,ObjectType/VariableType/DataType
└── Views             // 自定义视图

实际开发中,绝大多数业务交互从Objects节点开始向下浏览。Objects节点是厂商暴露给客户端的实例对象根,承载设备模型的可访问入口。

3.2 Browse / BrowseNext 操作

Browse服务返回某个节点的所有子引用(按引用类型过滤)。如果子节点过多,服务器返回一个continuationPoint,客户端用BrowseNext继续获取下一批。这一机制类似数据库分页,避免单次返回过大数据量。

// Browse 请求伪结构
Browse {
    NodesToBrowse: [
        {
            NodeId: "i=85"           // Objects节点
            BrowseDirection: Forward // Forward/Inverse/Both
            ReferenceTypeId: "i=33"  // References(所有引用的基类型)
            IncludeSubtypes: true
            NodeClassMask: 0         // 不过滤节点类
            ResultMask: 63           // 返回所有属性
        }
    ],
    MaxReferencesToReturn: 100       // 单次最大返回数
}

// Browse 响应
BrowseResponse {
    References: [
        { ReferenceTypeId: "i=35", // HasComponent
          NodeId: "ns=2;s=DeviceA",
          BrowseName: "2:DeviceA",
          DisplayName: "设备A",
          NodeClass: Object,
          IsForward: true }
    ],
    ContinuationPoint: [...]        // 为空表示已全部返回
}

3.3 NodeId 的格式与解析

NodeId是节点的全局唯一标识,由NamespaceIndex + IdentifierType + Identifier构成。IdentifierType有四种:Numeric(数字,常用在标准节点)、String(字符串,厂商常用)、Guid、Opaque(字节串)。客户端代码中应避免硬编码NodeId字符串,而应通过Browse + BrowseName匹配的方式定位节点,以适应不同服务器的命名空间差异。

四、数据访问模式:Read / Write / 历史访问

4.1 Read / Write 操作

OPC UA的Read服务支持一次性读取多个节点的属性值(通常是Value属性),Write服务同理。这种批量操作比经典OPC的SyncRead更高效,单次请求可处理上百个点位。

读操作参数说明
NodesToRead节点ID + 属性ID(一般是Value=13)的数组
MaxAge可接受的最大数据年龄(毫秒),0表示读取最新值
TimestampsToReturn返回的时间戳类型:Source/Server/Both/Neither

响应中每个节点对应一个DataValue,包含Value(Variant变体)、StatusCode(状态码,如Good/Bad/Uncertain)、SourceTimestamp(数据源时间)、ServerTimestamp(服务器接收时间)。StatusCode是排错的关键,0x00000000表示Good,0x80000000起为Bad。

4.2 历史数据访问(HA)

支持历史访问的服务器在变量节点上配置Historizing=true,并启用HA服务。客户端通过HistoryRead服务按时间范围查询历史数据,支持RawValues(原始值)、ProcessedData(聚合,如平均值、最大值)、ModifiedValues(被修改的值)等模式。这是OPC UA相对Modbus的显著优势——Modbus只能读到"当前值",OPC UA能直接拿到设备历史曲线,省去上位机自建数据库的开销。

4.3 属性访问

除Value外,变量节点的属性还包括DataType、ValueRank、ArrayDimensions、EURange(量程上下限)、EngineeringUnits(工程单位)、Description等。客户端通过Read + 属性ID(如EURange=69)获取元数据,用于UI显示量程、单位,避免硬编码。

五、订阅与通知机制

订阅机制是OPC UA相对Modbus的另一个核心优势。Modbus上位机必须轮询(polling)每个点位,几百个点位的扫描周期就会拉长。OPC UA支持订阅-通知模式:客户端订阅感兴趣节点,数据变化时服务器主动推送,显著降低网络流量和CPU占用。

5.1 Subscription → MonitoredItem → Notification 三层架构

OPC UA的订阅模型分三层:

  • Subscription(订阅):客户端创建的订阅对象,有独立的PublishingInterval(发布间隔)和优先级。一个会话可创建多个订阅。
  • MonitoredItem(监控项):挂载在订阅下的监控单元,指定要监控的Node+Attribute+采样间隔+触发条件。
  • Notification(通知):服务器在PublishingInterval到达时,把所有MonitoredItem产生的Notification打包成PublishResponse推送。
客户端                              服务器
  │                                   │
  │── CreateSubscription ──────────→  │  创建订阅
  │←── SubscriptionId ──────────────  │
  │                                   │
  │── CreateMonitoredItems ─────────→ │  添加监控项
  │←── MonitoredItemIds ────────────  │
  │                                   │
  │       (数据持续变化采样)           │
  │                                   │
  │←── Publish (Notification) ──────  │  服务器按间隔推送
  │── PublishAck ──────────────────→  │
  │←── Publish (Notification) ──────  │
  │── PublishAck ──────────────────→  │

5.2 发布间隔与采样间隔

需要区分两个间隔:PublishingInterval是订阅级别的发布周期,决定服务器多久向客户端推送一次;SamplingInterval是MonitoredItem级别的采样周期,决定服务器多久读取一次数据源。两者独立配置——SamplingInterval可以比PublishingInterval短,中间的多个采样值进入队列等待推送。

5.3 队列策略与触发条件

每个MonitoredItem维护一个队列,默认先进先出(FIFO),队列长度可配置(如10)。触发条件由MonitoringMode和Trigger决定:

触发模式含义典型场景
Reporting采样后入队,发布时推送常规数据监控
Sampling采样但不入队,不推送暂停订阅但保留采样
Disabled不采样不推送完全停用监控项

数据变化触发(DataChangeTrigger)可细化为:Status(仅状态变化推送)、Status/Value(状态或值变化推送,默认)、Status/Value/Timestamp(含时间戳变化)。DeadbandValue(死区)可设置数据变化幅度阈值,避免微小波动产生大量通知。

六、OPC UA安全机制

OPC UA安全机制覆盖传输层、消息层、应用层、用户层四个层次,是工业通信协议中较为完善的安全体系。

6.1 消息安全模式

OPC UA定义三种MessageSecurityMode:

  • None:无加密无签名,仅用于内网调试,禁止用于生产环境。
  • Sign:消息签名(SHA-1/SHA-256),防篡改但不加密,适合带宽敏感场景。
  • SignAndEncrypt:签名+加密(AES-128/256),生产环境推荐配置。

6.2 安全策略

安全策略(SecurityPolicy)定义具体的加密算法组合,常见有:

安全策略URI对称加密非对称加密签名
None
Basic128Rsa15AES-128RSA-15SHA-1
Basic256AES-256RSA-15SHA-1
Basic256Sha256AES-256RSA-OAEPSHA-256
Aes128Sha256RsaOaepAES-128RSA-OAEPSHA-256

新项目建议优先选择Basic256Sha256或Aes128Sha256RsaOaep,SHA-1已被业界认为不够安全。

6.3 证书认证流程

OPC UA使用X.509证书实现双向认证。首次连接时,客户端和服务器各自持有自签名或CA签发的证书,双方交换证书后由管理员确认信任关系,证书指纹被加入对方信任列表。这一过程称为"证书握手":

  1. 客户端发起OpenSecureChannel,附带自己的证书和签名。
  2. 服务器校验证书(自签名需人工信任,CA签名可自动验证)。
  3. 服务器返回自己的证书,客户端执行对称校验。
  4. 双方协商出对称密钥,用于后续消息加密。
  5. CreateSession阶段再做用户身份认证(匿名/用户名密码/证书)。

七、上位机OPC UA客户端开发(C# 示例)

OPC基金会官方提供Opc.Ua.Sdk(C#),开源社区有Opc.Ua.Client、MQTTnet等SDK。下面以Opc.Ua.Sdk为例,演示连接服务器并订阅节点的完整流程。

7.1 配置端点与建立连接

using Opc.Ua;
using Opc.Ua.Client;

public class OpcUaClientService
{
    private Session _session;
    private Subscription _subscription;

    public async Task<bool> ConnectAsync(string endpointUrl)
    {
        // 1. 获取端点描述
        var endpointDescription = CoreClientUtils.SelectEndpoint(endpointUrl, useSecurity: true);
        var endpointConfig = EndpointConfiguration.Create();
        var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfig);

        // 2. 创建应用配置(含证书处理)
        var appConfig = new ApplicationConfiguration
        {
            ApplicationName = "WinshiOpcUaClient",
            ApplicationType = ApplicationType.Client,
            SecurityConfiguration = new SecurityConfiguration
            {
                ApplicationCertificate = new CertificateIdentifier
                {
                    StoreType = "X509Store",
                    StorePath = "CurrentUser\\My",
                    SubjectName = "WinshiOpcUaClient"
                },
                TrustedPeerCertificates = new CertificateTrustList
                {
                    StoreType = "Directory",
                    StorePath = "Trusted"
                },
                RejectedCertificateStore = new CertificateTrustList
                {
                    StoreType = "Directory",
                    StorePath = "Rejected"
                },
                AutoAcceptUntrustedCertificates = false  // 生产环境关闭
            }
        };
        await appConfig.LoadApplicationInstanceCertificate(false);

        // 3. 创建会话
        var identity = new UserIdentity("admin", "password");
        _session = await Session.Create(
            appConfig, endpoint, updateBeforeConnect: true,
            sessionName: "WinshiOpcUaSession",
            sessionTimeout: 60000, identity, preferredLocales: null);

        _session.KeepAlive += (s, e) =>
        {
            if (e.ServiceResult != StatusCode.Good)
                Console.WriteLine($"[连接异常] {e.ServiceResult}");
        };
        return _session.Connected;
    }
}

7.2 创建订阅与监控项

public void SubscribeNodes(List<string> nodeIds, Action<string, object> onChanged)
{
    _subscription = new Subscription(_session.DefaultSubscription)
    {
        PublishingInterval = 500,        // 500ms发布一次
        KeepAliveCount = 10,
        LifetimeCount = 30,
        MaxNotificationsPerPublish = 1000,
        PublishingEnabled = true
    };
    _session.AddSubscription(_subscription);

    foreach (var nodeIdStr in nodeIds)
    {
        var item = new MonitoredItem
        {
            DisplayName = nodeIdStr,
            StartNodeId = new NodeId(nodeIdStr),
            AttributeId = Attributes.Value,
            SamplingInterval = 200,        // 200ms采样一次
            QueueSize = 10,
            DiscardOldest = true,
            MonitoringMode = MonitoringMode.Reporting
        };
        item.Notification += (sender, args) =>
        {
            var notification = (MonitoredItemNotification)args.NotificationValue;
            onChanged?.Invoke(nodeIdStr, notification.Value.Value);
        };
        _subscription.AddItem(item);
    }
    _subscription.Create();
}

几个生产环境的实践建议:

  • PublishingInterval与SamplingInterval根据实际刷新需求设定,过短会增加服务器和带宽负担,过长则响应慢。常见组合是发布500ms、采样200ms。
  • 不要把所有点位都塞进同一个MonitoredItem的队列,建议按设备分组、按业务模块分组创建多个订阅,便于单独控制启停。
  • KeepAlive机制用于检测断连,KeepAliveCount次未收到Publish即认为连接异常,触发重连。

八、OPC UA vs Modbus 对比

OPC UA和Modbus TCP都是工业以太网通讯主流协议,选型时需要综合考量实时性、扩展性、开发成本。下表是赢式科技团队在3000+项目中的归纳:

对比维度Modbus TCPOPC UA
协议复杂度简单(功能码+地址)复杂(信息模型/订阅/安全)
实时性较好(轻量、低延迟)略逊(开销大,TSN可改善)
扩展性弱(点位扁平,无语义)强(对象化模型,含语义)
安全机制无内置安全(依赖网络隔离)证书+加密+签名+用户认证
历史数据不支持,需上位机自建原生支持HA访问
事件机制无,只能轮询支持订阅通知与事件订阅
跨平台支持广泛(极轻量)广泛(成熟SDK)
开发难度低(一次掌握即可)较高(需理解信息模型)
典型场景单设备数据采集、小型系统多设备集成、MES/SCADA集成、智能制造
许可费用免费开源开源SDK免费,商用SDK需付费

选型建议:单一PLC或少量设备的数据采集,Modbus TCP仍是性价比之选;多品牌、多设备集成场景,OPC UA的语义模型、订阅机制和安全体系优势明显。许多新项目采用"网关聚合"方案——现场层用Modbus/PROFINET,网关之上统一OPC UA对外提供数据,兼顾实时性与可管理性。

九、赢式科技OPC UA上位机开发服务

上海赢式信息科技有限公司2010年成立,16年专注上位机软件开发与工业物联网,累计交付3000+定制化项目,覆盖汽车零部件、食品饮料、新能源电池、智慧能源、冷链物流、环保监测等行业。公司在OPC UA客户端开发、PLC网关集成、SCADA/MES数据中台方面有较多项目积累,支持西门子、倍福、罗克韦尔、三菱等多品牌OPC UA服务器对接。在苏州、无锡、济南、西安设有本地服务网点,承诺完整源码交付、1年免费维护、2小时电话响应、24小时现场到达。如果您有OPC UA对接、上位机开发或工业通讯相关的项目需求,可以联系赢式科技获取需求评估与方案报价。