快速开始 (C#)
5 分钟从零开始用 Darra OPC UA Client SDK 连上 OPC UA Server, 跑通连接 / 浏览 / 读写 / 订阅 / 方法调用全流程. 本页用 C# 为示例, 其他语言用法见 六语言 SDK 文档.
- 启用加密 — Sign / SignAndEncrypt + PFX 证书
- Discovery 发现 — GetEndpoints / FindServers
- History 历史访问 — HistoryRead 5 模式
- Event 事件订阅 — Alarms & Conditions
一、准备工作
在开始第一个 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· Javaxyz.darra:darra-opcua· C++ CMake · Rustcargo add darra-opcua· Ccmake --install. 详见 六语言安装.
3. 准备一个 OPC UA Server
本 SDK 是 Client only, 联调需要一个 Server. 任选一个:
| 方案 | 端点 | 适用 |
|---|---|---|
| Darra SimulatorServer (推荐) | opc.tcp://localhost:4840 | 开发调试, 内置温度/计数器/Add 方法等 |
| Darra Software PLC 内置 Server | opc.tcp://<PLC>:4840 | 验证真实 PLC 接入 |
| 公网测试 Server | opc.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 安全模式 (开发期最简). 生产环境必须改Sign或SignAndEncrypt, 见 安全配置. 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 部分:Value—Variant类型的实际值, 用AsDouble/AsInt32/AsString/AsDateTime等 setter 取强类型Status—StatusCode,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— 方法本身的 NodeIdinputs—Variant[]入参列表, 顺序必须与 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函数返回时,ua的using块结束, SDK 自动:- 取消所有未完成的订阅 (DeleteSubscriptions)
- 关闭 Session (CloseSession)
- 关闭 SecureChannel (CloseSecureChannel)
- 释放 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 桥接 · 大规模监控 |