冗余 Server 自动切换
工业现场通常配主备 (Hot Standby) Server. 主故障时客户端切到备, 业务继续. 用 C# 示例.
配套示例
- 大批量监控 → 大规模监控
- 桥接到 Modbus → OPC UA → Modbus TCP 桥接
完整代码
using DarraOpcUa_Client;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
class FailoverClient : IDisposable
{
private readonly string[] _endpoints;
private readonly IReadOnlyList<string> _tags;
private DarraOpcUa _ua;
private OpcUaSubscription _sub;
private int _currentIdx = -1;
private readonly object _lock = new();
public FailoverClient(IReadOnlyList<string> tags, params string[] endpoints)
{
_tags = tags;
_endpoints = endpoints;
}
public void Connect()
{
lock (_lock)
{
// 从下一个 endpoint 开始尝试 (避免反复连同一个故障点)
for (int tries = 0; tries < _endpoints.Length; tries++)
{
_currentIdx = (_currentIdx + 1) % _endpoints.Length;
var ep = _endpoints[_currentIdx];
try
{
_ua = new DarraOpcUa(ep);
_ua.Events.CommunicationError += OnCommError;
_ua.Connect();
Console.WriteLine($"[OK] 已连接 {ep}");
Resubscribe();
return;
}
catch (OpcUaException ex)
{
Console.WriteLine($" {ep} 连接失败: {ex.StatusCode}");
_ua?.Dispose();
_ua = null;
}
}
throw new Exception("所有 endpoint 都不可达");
}
}
private void Resubscribe()
{
_sub = _ua.CreateSubscription(500);
_sub.DataChanged += (s, e) =>
Console.WriteLine($" {e.NodeId} = {e.ValueString} [{e.Status}]");
_sub.AddMany(_tags);
}
private void OnCommError(object sender, CommunicationErrorEventArgs e)
{
Console.WriteLine($"\n[!!] 通讯异常 {e.StatusCode}, 切换 server...");
Task.Run(() =>
{
try { _sub?.Dispose(); } catch { }
try { _ua?.Dispose(); } catch { }
Thread.Sleep(2000); // 等故障检测稳定, 避免反复切换抖动
try { Connect(); }
catch (Exception ex) { Console.WriteLine($"切换失败: {ex.Message}, 30 秒后重试"); }
});
}
public void Dispose() { _sub?.Dispose(); _ua?.Dispose(); }
}
class Program
{
static async Task Main()
{
var tags = new[] { "ns=2;s=Boiler1.Temperature", "ns=2;s=Boiler1.Pressure" };
using var client = new FailoverClient(
tags,
"opc.tcp://primary:4840",
"opc.tcp://backup:4840");
client.Connect();
Console.WriteLine("\n运行中. 拔主 server 网线测试切换. Ctrl+C 退出.\n");
await Task.Delay(Timeout.Infinite);
}
}
分段说明
第 1 步: CommunicationError 事件触发切换
_ua.Events.CommunicationError += OnCommError;
SDK 内部 KeepAlive 检测到通讯异常时, 触发 CommunicationError 事件. 不需要主动 ping 服务端.
第 2 步: 切换前等 2 秒
Thread.Sleep(2000); // 等故障检测稳定
关键: 故障可能是网络抖动 (瞬断), 立即切换会反复抖动 (主→备→主→备). 加 2 秒 backoff 让网络稳定. 实际项目可以加指数退避.
第 3 步: 切换后必须重建订阅
private void Resubscribe()
{
_sub = _ua.CreateSubscription(500);
_sub.AddMany(_tags);
}
切到新 Server 后, 旧 Session / Subscription 全部失效. 必须重建订阅 + 重新 AddMany. 高级用法: 如果主备 Server 共享 SubscriptionId (Hot Standby 配置), 可以用
TransferSubscriptions接管旧订阅, 不用重建 MI. 大多数实际部署达不到这个一致性, 重建更可靠.
注意事项
- 主备 Server 必须同 NamespaceArray (NodeId 解析一致), 否则切换后
ns=2;...在新 server 可能映射到完全不同的节点. - KeepAliveCount 默认 ~10, 检测延迟 =
publishingInterval * KeepAliveCount(默认 ~5 秒). 要更快检测就调小, 但容易误判. - 长期运行还要处理"新主 (原备) 也挂了"的连环失败, 上面代码用循环索引 + 异常重试覆盖.