订阅模型 (Subscription)
OPC UA 的订阅模型解决 "客户端怎么实时收到服务端数据变化" 这个问题, 比轮询 Read 高效得多.
配套阅读
- 服务集 — Subscription / MonitoredItem 服务清单
- 示例 — 多点订阅采集 — 跑通最小订阅
- 示例 — 大规模监控 — 1000+ MI 调优
概念结构
Session (1) ── owns ──▶ Subscription (N) ── contains ──▶ MonitoredItem (M)
│
└── publishes ──▶ NotificationMessage
- Subscription 是发布上下文, 决定 publishing interval (服务端推送间隔)
- MonitoredItem (MI) 是单个监控目标 (一个 NodeId + Attribute), 决定 sampling interval
- NotificationMessage 是服务端的推送数据包, 包含一批 MI 的最新值
Publish / Republish 机制
OPC UA 是"客户端拉取" — 客户端需要不断发送 PublishRequest, 服务端在有新数据时返回 PublishResponse 携带 NotificationMessage.
Client Server
│ ──PublishRequest──────────────▶ │
│ │ (no data, hold)
│ ──PublishRequest──────────────▶ │
│ ◀──PublishResponse(NotifMsg1, seq=10)
│ ──PublishRequest+ack(seq=10)──▶ │
│ ◀──PublishResponse(NotifMsg2, seq=11)
│ ...
Darra OPC UA Client SDK 启用 AutoPublish = true 后, 内部后台线程自动维持 Publish 队列, 用户无需操心.
如果某个 NotificationMessage 因为网络问题客户端没收到, 客户端可以发 Republish(seqNo) 让服务端重发该 SequenceNumber 的消息 (服务端默认缓存近 N 条).
Subscription 关键参数
| 参数 | 默认 | 说明 |
|---|---|---|
| publishingIntervalMs | 500 | 服务端推送的最小间隔 (实际可被 Server revise) |
| lifetimeCount | 1200 | 多少个 publish 周期没收到 PublishRequest 就认为客户端死了 |
| maxKeepAliveCount | 50 | 没新数据时多久发一次空 KeepAlive 通知 |
| priority | 0 | 优先级 (一个 Session 上多个订阅时) |
服务端会对这些参数 "revise" (调整到自己能接受的范围), Modify 调用返回 RevisedXxx 告诉客户端实际生效值.
MonitoredItem 关键参数
| 参数 | 默认 | 说明 |
|---|---|---|
| samplingIntervalMs | -1 (跟订阅) | 服务端采样的最小间隔 |
| queueSize | 1 | 服务端为该 MI 缓冲多少个未发数据点 |
| discardOldest | true | 队列满时丢最旧 / 最新 |
| monitoringMode | Reporting | Disabled (停采) / Sampling (采但不推) / Reporting (采且推) |
| filter | DataChangeFilter | 数据变化触发条件 (绝对死区 / 百分比死区) |
DataChangeFilter (死区)
不是任何细微变化都要推送 — 配置死区可以减少噪声:
| DeadbandType | 含义 |
|---|---|
| None | 任意变化都推送 |
| Absolute | 数值变化超过绝对值阈值才推送 |
| Percent | 数值变化超过 EURange 的百分比才推送 |
TransferSubscriptions (跨 Session 迁移)
会话断开重连后, 旧 Subscription 在服务端可能还活着 (没被 Cleanup), 新 Session 可以用 TransferSubscriptions 把它们转过来, 不需要重建全部 MI:
ua_new.TransferSubscriptions(oldSubscriptionIds, sendInitialValues: true);
返回每个订阅的 StatusCode, Good 表示转移成功. 转移后立即推一次当前值 (sendInitialValues=true), 客户端就能直接收到新通知.
一次性 vs 长期订阅
- 短期: 订完用完 Dispose, 服务端清理
- 长期: KeepAlive 自动维持, 直到 Session 断开
- 极长期 (跨 Session): 配合 TransferSubscriptions 重连后接管