跳到主要内容

订阅自恢复 (TransferSubscriptions / Recreate)

概述

Session 重连后, 服务端的 Subscription 可能仍存活 (在 LifetimeCount × PublishingInterval 内), 也可能已被清理。SDK 在重连成功后会 两步走:

  1. TransferSubscriptions (Part 4 §5.13.7) — 把旧 SubscriptionId 转给新 Session
  2. Recreate — 转移失败时, 用本地缓存的 MonitoredItemSpec 重建 Sub + 全部 MI (含 DataChangeFilter / EventFilter / Triggering)

业务侧 无需 手动处理, 全程通过事件通道知会 UI。

对应规范段: Part 4 §5.13.7 (TransferSubscriptions) / §5.12 (CreateSubscription)。

API

方法 / 事件类别读写说明
ua.TransferSubscriptions(oldSubIds, sendInitialValues)方法把旧 sub 转给当前 session, 通常 SDK 自动调
ua.Events.SubscriptionLost事件订阅丢失 (服务端清理 / lifetime 超时)
ua.Events.SubscriptionRestored事件订阅恢复成功 (Transfer 或 Recreate)

代码示例

using DarraOpcUa_Client;

using var ua = new DarraOpcUa("opc.tcp://localhost:4840");
ua.Connect();

// 1) 挂事件
ua.Events.SubscriptionLost += (s, e) =>
{
Logger.Warn($"订阅丢失: subId={e.SubscriptionId}, 原因={e.Reason}");
};
ua.Events.SubscriptionRestored += (s, e) =>
{
Logger.Info($"订阅已恢复: {e.Source}");
};

// 2) 创建订阅 + MI, 全程不变
using var sub = ua.CreateSubscription(500);
uint mi = sub.Add("ns=2;s=Temp", AttributeId.Value, 200,
MonitoringMode.Reporting, 10, true,
new DataChangeFilter { DeadbandType = DeadbandType.Absolute, DeadbandValue = 0.5 });

sub.DataChanged += (s, e) =>
{
Console.WriteLine($"{e.Value.Value} @ {e.Value.SourceTimestamp}");
};

// 3) 现在拔网线 30 秒, 看日志:
// Reconnecting → Reconnected → SubscriptionRestored
// DataChanged 自动续上, Filter 也保留

// 4) 手动 Transfer (业务知道服务端短暂复位但 sub 还活着)
var oldIds = new uint[] { sub.SubscriptionId };
var rcs = ua.TransferSubscriptions(oldIds, sendInitialValues: true);
foreach (var rc in rcs) Console.WriteLine($"Transfer: {rc}");

恢复流程

重连成功

对每个本地 Subscription:
1. 调 TransferSubscriptions(oldSubId)
├─ Good → SubscriptionRestored 事件, 完成
└─ BadSubscriptionIdInvalid → 进入 2
2. CreateSubscription(originalParams) 拿到新 subId
3. 按本地 _miCache 顺序 Recreate 所有 MI
├─ 还原 SamplingInterval / Mode / QueueSize / DiscardOldest
├─ 还原 DataChangeFilter
├─ 还原 EventFilter (事件订阅)
└─ 还原 Triggering Links
4. SubscriptionRestored 事件

缓存的字段

_miCache 保留每个 MI 的:

  • NodeId / AttributeId
  • SamplingIntervalMs
  • MonitoringMode (Disabled / Sampling / Reporting)
  • QueueSize / DiscardOldest
  • DataChangeFilter (Trigger + Deadband)
  • EventFilter (SelectClauses + WhereClause)

恢复时这些字段全部重放, 服务端 MI Id 会变化, 业务侧应使用 ClientHandle 而非 server-side mi id 对账

最佳实践

  • 业务用 ClientHandlesub.Add 返回的 mi 在 Recreate 后会变, 不要持久化
  • 挂 SubscriptionRestored 事件 — UI 可显示"已恢复, X 个监控项"
  • 不要在 SubscriptionLost 里手动 Recreate — SDK 已经在做, 重复会冲突
  • 设置合理 lifetimelifetimeCount × publishingInterval 太短, 网络小波动就丢 sub; 太长, 服务端资源累积
  • 关键参数变更后 Pause 再 Resume — 不要中途改 sub 参数, SDK 缓存可能不同步

跨语言对照

C#PythonJavaC++RustC
TransferSubscriptionstransfer_subscriptionstransferSubscriptionsTransferSubscriptionstransfer_subscriptionsDarraUa_Session_TransferSubscriptions
ua.Events.SubscriptionLostevents.on('subscription_lost', ...)events.onSubscriptionLost(...)events.OnSubscriptionLost(...)events.on_subscription_lost(...)DarraUa_RegisterSubLostHandler
ua.Events.SubscriptionRestoredevents.on('subscription_restored', ...)events.onSubscriptionRestored(...)events.OnSubscriptionRestored(...)events.on_subscription_restored(...)DarraUa_RegisterSubRestoredHandler

下一步