订阅自恢复 (TransferSubscriptions / Recreate)
概述
Session 重连后, 服务端的 Subscription 可能仍存活 (在 LifetimeCount × PublishingInterval 内), 也可能已被清理。SDK 在重连成功后会 两步走:
- TransferSubscriptions (Part 4 §5.13.7) — 把旧 SubscriptionId 转给新 Session
- 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 对账。
最佳实践
- 业务用 ClientHandle —
sub.Add返回的mi在 Recreate 后会变, 不要持久化 - 挂 SubscriptionRestored 事件 — UI 可显示"已恢复, X 个监控项"
- 不要在 SubscriptionLost 里手动 Recreate — SDK 已经在做, 重复会冲突
- 设置合理 lifetime —
lifetimeCount × publishingInterval太短, 网络小波动就丢 sub; 太长, 服务端资源累积 - 关键参数变更后 Pause 再 Resume — 不要中途改 sub 参数, SDK 缓存可能不同步
跨语言对照
| C# | Python | Java | C++ | Rust | C |
|---|---|---|---|---|---|
| TransferSubscriptions | transfer_subscriptions | transferSubscriptions | TransferSubscriptions | transfer_subscriptions | DarraUa_Session_TransferSubscriptions |
| ua.Events.SubscriptionLost | events.on('subscription_lost', ...) | events.onSubscriptionLost(...) | events.OnSubscriptionLost(...) | events.on_subscription_lost(...) | DarraUa_RegisterSubLostHandler |
| ua.Events.SubscriptionRestored | events.on('subscription_restored', ...) | events.onSubscriptionRestored(...) | events.OnSubscriptionRestored(...) | events.on_subscription_restored(...) | DarraUa_RegisterSubRestoredHandler |