跳到主要内容

快速开始 (C#)

5 分钟从零开始用 Darra OPC UA Client SDK 连上 OPC UA Server, 跑通连接 / 浏览 / 读写 / 订阅 / 方法调用全流程. 本页用 C# 为示例, 其他语言用法见 六语言 SDK 文档.

进阶专题

一、准备工作

在开始第一个 Demo 之前, 准备好以下三样东西.

1. 环境

要求
操作系统Windows 10 / 11, Linux, macOS 任一
.NET.NET Framework 4.6.1+.NET 6 / 8 / 9
端口OPC UA 默认 4840/tcp, 防火墙放通

2. 安装 SDK (NuGet)

在你的 .NET 项目目录执行:

dotnet add package DarraOpcUa

NuGet 会自动把 DarraOpcUa.dll + 原生库 Darra.OpcUa.Core.dll 一起复制到输出目录, 无需额外操作.

其他语言: Python pip install darra-opcua · Java xyz.darra:darra-opcua · C++ CMake · Rust cargo add darra-opcua · C cmake --install. 详见 六语言安装.

3. 准备一个 OPC UA Server

本 SDK 是 Client only, 联调需要一个 Server. 任选一个:

方案端点适用
Darra SimulatorServer (推荐)opc.tcp://localhost:4840开发调试, 内置温度/计数器/Add 方法等
Darra Software PLC 内置 Serveropc.tcp://<PLC>:4840验证真实 PLC 接入
公网测试 Serveropc.tcp://opcua.demo-this.com:51210/UA/SampleServer临时验证, 网络可达即可
第三方开源open62541 / node-opcua / Eclipse Milo自带 example server

启动 Server 后, 用任一 OPC UA Client (UaExpert / Darra OPC UA Client GUI) 浏览 Objects/Server/... 验证可达, 即可进入下一节.


二、第一个完整程序

下面是一个完整的 C# 控制台程序, 一次性演示 OPC UA 客户端的核心使用流程:

安装 → 创建会话 → 连接 → 浏览 → 读 → 写 → 订阅 → 方法调用 → 断开.

把它复制到任意 .NET 项目跑起来, 然后下面我们一段一段拆开, 解释每段代码的作用.

using DarraOpcUa_Client;
using System;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
// ========== 1. 创建会话 ==========
using var ua = new DarraOpcUa("opc.tcp://localhost:4840");
ua.Events.Connected += (s, e) => Console.WriteLine($"[OK] 已连接 {e.EndpointUrl}");
ua.Events.Disconnected += (s, e) => Console.WriteLine("[--] 已断开");

// ========== 2. 连接服务器 ==========
ua.Connect();
Console.WriteLine($"会话状态: {ua.State}");

// ========== 3. 浏览节点 ==========
Console.WriteLine("\n--- Objects 文件夹下的子节点 ---");
foreach (var r in ua.Browse("i=85"))
Console.WriteLine($" {r.NodeId,-22} [{r.NodeClass}] {r.DisplayName}");

// ========== 4. 读单节点 ==========
using (var dv = ua.Read("i=2258")) // i=2258 = ServerStatus.CurrentTime
Console.WriteLine($"\n服务器时间 = {dv.Value.AsDateTime} [{dv.Status}]");

// ========== 5. 写单节点 ==========
var st = ua.Write("ns=2;s=Counter", new Variant(42));
Console.WriteLine($"写入 Counter=42, 返回状态 = {st}");

// ========== 6. 订阅数据变化 ==========
using var sub = ua.CreateSubscription(publishingIntervalMs: 500);
sub.DataChanged += (s, e) =>
Console.WriteLine($" [推送] {e.NodeId} = {e.ValueString} ({e.Status})");
sub.Add("i=2258"); // 订阅服务器时间, 每秒会变
sub.Add("ns=2;s=Counter");

Console.WriteLine("\n收 5 秒订阅推送...");
await Task.Delay(5000);

// ========== 7. 方法调用 ==========
try
{
var inputs = new Variant[] { new Variant(3), new Variant(5) };
var outputs = ua.Call("ns=2;s=Calculator", "ns=2;s=Calculator.Add", inputs);
foreach (var o in outputs)
using (o) Console.WriteLine($"\n调用 Add(3, 5) = {o.AsInt32}");
}
catch (OpcUaException ex)
{
Console.WriteLine($"\n方法调用失败: {ex.StatusCode}");
}

// ========== 8. 断开 (using 自动释放) ==========
Console.WriteLine("\n程序结束, using 块结束时自动 Disconnect + Dispose.");
}
}

下面分段拆解, 解释每一段代码的功能与注意事项.


第 1 步: 创建会话

using var ua = new DarraOpcUa("opc.tcp://localhost:4840");
ua.Events.Connected += (s, e) => Console.WriteLine($"[OK] 已连接 {e.EndpointUrl}");
ua.Events.Disconnected += (s, e) => Console.WriteLine("[--] 已断开");

这段做什么: 实例化 SDK 主类 DarraOpcUa, 并挂上连接 / 断开事件回调.

  • DarraOpcUa 是 SDK 一切操作的入口, 每个 OPC UA 会话对应一个实例.
  • 构造函数最简形式只需要 endpointUrl, 默认匿名 + None 安全模式 (开发期最简). 生产环境必须改 SignSignAndEncrypt, 见 安全配置.
  • using 关键字至关重要: 退出 Main 时自动调用 Disconnect + Dispose, 释放 native Session 句柄, 避免内存泄漏.
  • ua.Events 是统一事件通道, 汇集会话生命周期 / 订阅生命周期 / 数据变化 / 异常等事件, 非常适合 UI 状态栏 / 日志面板. 完整事件清单见 Session Events.

第 2 步: 连接服务器

ua.Connect();
Console.WriteLine($"会话状态: {ua.State}");

这段做什么: 触发 OPC UA 标准握手序列, 建立 Session.

  • Connect() 内部串行执行: Hello → OpenSecureChannel → GetEndpoints → CreateSession → ActivateSession → 加载 NamespaceArray.
  • 默认 connectTimeoutMs = 10_000 (10 秒), 超时未完成抛 OpcUaException 携带具体 StatusCode.
  • 实际项目应该 try / catch 包裹, 处理 BadConnectionRejected / BadCertificateInvalid / BadIdentityTokenRejected 等典型失败.
  • 连接成功后, ua.State 进入 Connected, 可以执行 Read / Write / Browse / Subscribe / Call.

第 3 步: 浏览节点 (Browse)

foreach (var r in ua.Browse("i=85"))
Console.WriteLine($" {r.NodeId,-22} [{r.NodeClass}] {r.DisplayName}");

这段做什么: 列出指定节点的直接子节点 (一层), 用于发现服务器对外暴露了哪些数据.

  • i=85 是 OPC UA 标准的 Objects 文件夹根, 任何 Server 都有, 自定义业务节点一般挂在它下面.
  • 每个返回的 OpcUaReference 含 4 个字段:
    • NodeId — 子节点 NodeId 字符串
    • NodeClass — 子节点类别 (Object / Variable / Method / View / ...)
    • BrowseName — 含命名空间前缀的名字 (如 2:Temperature)
    • DisplayName — 人类可读名
  • 后续可对感兴趣的节点继续 Browse 下钻, 形成树形导航 (典型客户端 GUI 的 "OPC UA 节点树" 就是这么实现的).
  • 一次大批量请求用 BrowseMany([n1, n2, ...]) 一次 RPC 拿回, 见 Nodes / Browse.

第 4 步: 读单节点 (Read)

using (var dv = ua.Read("i=2258"))
Console.WriteLine($"服务器时间 = {dv.Value.AsDateTime} [{dv.Status}]");

这段做什么: 读取一个节点的 Value 属性 (默认 AttributeId.Value = 13).

  • i=2258 = Server_ServerStatus_CurrentTime, 是 OPC UA 标准节点, 任何 Server 都有.
  • Read 返回 DataValue, 包含 4 部分:
    • ValueVariant 类型的实际值, 用 AsDouble / AsInt32 / AsString / AsDateTime 等 setter 取强类型
    • StatusStatusCode, Good 表示有效
    • SourceTimestamp — 数据源时间
    • ServerTimestamp — 服务端打包时间
  • DataValue 必须 using: 它持有 native 内存句柄, 不释放会内存泄漏.
  • 也可以读其他属性: ua.Read("ns=2;s=Temperature", AttributeId.DisplayName).
  • 一次读多个节点用 ReadMany([n1, n2, ...]), 比循环 Read 快 N 倍.

第 5 步: 写单节点 (Write)

var st = ua.Write("ns=2;s=Counter", new Variant(42));
Console.WriteLine($"写入 Counter=42, 返回状态 = {st}");

这段做什么: 把一个值写入服务端的指定节点.

  • Variant 是 SDK 通用值容器, 直接 new Variant(42) (Int32) / new Variant(3.14) (Double) / new Variant("text") (String) / new Variant(new[]{1.0, 2.0}) (Double 数组) 即可, C# 编译器根据参数类型自动匹配重载. 完整类型表见 Variant.
  • Write 返回 StatusCode:
    • Good — 写入成功
    • BadTypeMismatch — 类型不对 (写 Int32 到 Double 节点等)
    • BadNotWritable — 节点是只读
    • BadUserAccessDenied — 当前用户没有写权限
  • Write 业务失败不抛异常, 通过返回值判断 (transport 级失败如断网才抛 OpcUaException).
  • 一次写多个节点用 WriteMany([(n1, v1), (n2, v2), ...]).

第 6 步: 订阅 (Subscription + MonitoredItem)

using var sub = ua.CreateSubscription(publishingIntervalMs: 500);
sub.DataChanged += (s, e) =>
Console.WriteLine($" [推送] {e.NodeId} = {e.ValueString} ({e.Status})");
sub.Add("i=2258");
sub.Add("ns=2;s=Counter");

await Task.Delay(5000);

这段做什么: 让服务端主动推送数据变化, 不再轮询 Read.

  • 订阅是 OPC UA 性能核心 — 100 个 Tag 用 Read 轮询是 100 次 RPC, 用订阅只在变化时单条推送.
  • CreateSubscription(500) 创建订阅, publishingIntervalMs = 500 表示服务端最多每 500ms 推一批.
  • sub.Add(nodeId) 加监控项 (MonitoredItem), 监听节点 Value 变化. 大批量用 sub.AddMany([...]).
  • DataChanged 事件在数据变化时触发, e 含扁平字段:
    • NodeId / ValueString / Value (Variant) / Status / SourceTimestamp / ServerTimestamp.
  • 回调在非 UI 线程触发 — WinForms 用 Control.Invoke, WPF 用 Dispatcher.Invoke 切回 UI 线程更新界面.
  • AutoPublish 默认开启, 后台线程自动跑 Publish, 不需要手动调用.
  • sub 也必须 using, 否则订阅泄漏 (服务端会保留订阅直到 sessionTimeoutMs 超时).
  • 完整参数 (samplingIntervalMs / queueSize / discardOldest / monitoringMode) 见 Subscription.

第 7 步: 方法调用 (Method Call)

var inputs  = new Variant[] { new Variant(3), new Variant(5) };
var outputs = ua.Call("ns=2;s=Calculator", "ns=2;s=Calculator.Add", inputs);
foreach (var o in outputs)
using (o) Console.WriteLine($"调用 Add(3, 5) = {o.AsInt32}");

这段做什么: 调用服务器端定义的 OPC UA Method (RPC 接口).

  • Call(objectNodeId, methodNodeId, inputs) — 三个参数:
    • objectNodeId — 方法所在对象的 NodeId (Method 必须挂在某个 Object 下)
    • methodNodeId — 方法本身的 NodeId
    • inputsVariant[] 入参列表, 顺序必须与 server 端 InputArguments 定义一致
  • 返回 Variant[], 顺序对应 server 端 OutputArguments.
  • 每个返回的 Variant 都持有 native 句柄, 必须 using (推荐 foreach + using 模式).
  • 找方法 NodeId 的两种方式:
    • 浏览过滤: ua.Browse("ns=2;s=Calculator", filter: NodeClass.Method)
    • 在 UaExpert / Darra OPC UA Client GUI 里手动找, 复制 NodeId
  • 失败处理:
    • 方法执行错 → 抛 OpcUaException 携带 BadInvalidArgument / BadUserAccessDenied / BadMethodInvalid
    • 网络层错 → 抛 OpcUaException 携带 BadCommunicationError

第 8 步: 断开 (using 自动)

// using var ua = ...   ← Main 结束时自动调用 Disconnect + Dispose

这段做什么: 关闭 Session, 释放所有 native 资源.

  • Main 函数返回时, uausing 块结束, SDK 自动:
    1. 取消所有未完成的订阅 (DeleteSubscriptions)
    2. 关闭 Session (CloseSession)
    3. 关闭 SecureChannel (CloseSecureChannel)
    4. 释放 native handle, GC
  • 也可以显式提前断开: ua.Disconnect() (例如用户点了"断开"按钮).
  • 断开时 Events.Disconnected 事件触发, 方便日志记录.
  • 绝不要忘 using — 不释放会留 native Session 句柄, 进程长期运行会内存泄漏.

三、跑起来效果

把上面完整程序保存为 Program.cs 放进一个 dotnet new console 项目, 加 dotnet add package DarraOpcUa, 启动 Darra SimulatorServer (或任一 4840 OPC UA Server), 然后 dotnet run. 预期输出大致如下:

[OK] 已连接 opc.tcp://localhost:4840
会话状态: Connected

--- Objects 文件夹下的子节点 ---
i=2253 [Object] Server
ns=2;s=Boiler1 [Object] Boiler1
ns=2;s=Calculator [Object] Calculator
ns=2;s=Counter [Variable] Counter

服务器时间 = 2026-04-25T08:00:00Z [Good]
写入 Counter=42, 返回状态 = Good

收 5 秒订阅推送...
[推送] i=2258 = 2026-04-25T08:00:00.500Z (Good)
[推送] ns=2;s=Counter = 42 (Good)
[推送] i=2258 = 2026-04-25T08:00:01.000Z (Good)
...

调用 Add(3, 5) = 8

程序结束, using 块结束时自动 Disconnect + Dispose.
[--] 已断开

四、下一步

想做的事看这里
加密连接 (Sign / SignAndEncrypt)拓展 — 启用加密
服务器发现 (GetEndpoints / FindServers)拓展 — Discovery 发现
历史数据 (HistoryRead 5 模式)拓展 — History 历史访问
报警与事件 (Alarms & Conditions)拓展 — Event 事件订阅
完整 C# API 参考C# SDK 总览
其他语言用法Java · Python · C++ · Rust · C
真实场景示例多点订阅采集 · 全流程到 CSV · Modbus 桥接 · 大规模监控

相关链接