资讯动态

.NET OPC UA通信Demo:连接、读写、订阅与断线重连实战

发布时间:2026/10/15 14:14:37 来源:尧图企业网站定制
简介这是一份面向.NET开发者与工业自动化工程师的OPC UA通信实战Demo围绕连接、断开、读写、订阅与心跳监听五大核心操作展开帮助读者快速掌握与PLC及自动化设备的数据交互方式。资源包共2000个文件以1218个xml配置与数据文件、252个dll程序集、112个txt说明、28个nupkg依赖包及10个cs源码为主另含sln解决方案与csproj工程文件压缩包约87.75MB结构完整便于直接编译调试。已有674人学习下载。通过该Demo可系统理解OPC UA客户端建立连接时的URL配置与证书验证、读写节点值的方法调用、订阅监视项的推送机制以及心跳回调在连接活性检测中的应用适合作为工业物联网项目对接OPC UA服务器的参考范例与排错起点。1. 从一次产线联调说起这个 .NET OPC UA Demo 到底能省多少事上个月帮一个做设备集成的朋友看现场他们那条产线要接三台不同品牌的 PLC上位机用 C# 写结果卡在 OPC UA 的会话保持上——连上五分钟就掉线重连逻辑写得一团糟订阅的数据还时不时丢包。这种场景其实特别典型.NET 做工业通信不是不行而是很多人第一次碰 OPC UA 的时候把「连上」当成了终点实际上连接、断开、读写、订阅、心跳监听这五件事每一件都有独立的坑。这份 .NET OPC UA 通信 Demo 就是冲着这个痛点来的它把五个核心动作拆成可运行的示例覆盖了从建立会话到异常断线重连的完整链路。适合谁做上位机开发的 .NET 工程师、需要对接 PLC 或 SCADA 的自动化从业者以及正在选型 OPC UA 客户端库的技术负责人。你不需要先啃完几万字的规范文档照着 Demo 跑一遍再回头理解参数效率会高很多。2. 环境搭建与连接会话从 NuGet 包到 Session 建立2.1 选型理由为什么是 OPCFoundation.NetStandard.Opc.Ua.NET 生态里做 OPC UA 客户端绕不开的就是官方基金会维护的OPCFoundation.NetStandard.Opc.Ua系列包。有人会问能不能用第三方封装我的建议是除非你有特殊需求否则优先用官方栈。原因有三点。第一协议栈的完整性官方包覆盖了 UA-TCP、HTTPS、PubSub 等多种传输方式第三方封装往往只做了最常用的 UA-TCP。第二证书和安全策略的支持OPC UA 的安全模型不是可选项很多现场要求 SignAndEncrypt第三方库在这块经常偷工减料。第三社区和文档出问题的时候你能搜到的答案大概率是基于官方包的。Demo 里用到的核心包有这么几个# 在项目目录下执行安装 OPC UA 客户端所需的核心包 dotnet add package OPCFoundation.NetStandard.Opc.Ua.Client dotnet add package OPCFoundation.NetStandard.Opc.Ua.Configuration dotnet add package OPCFoundation.NetStandard.Opc.Ua.Security.Certificates这三个包的分工要搞清楚。Opc.Ua.Client提供Session、Subscription、MonitoredItem这些核心类Opc.Ua.Configuration负责应用配置和证书管理Opc.Ua.Security.Certificates处理证书的生成和验证。少装一个编译时就会报类型找不到。2.2 建立连接的完整代码与参数说明连接 OPC UA 服务端不是一句Connect()就完事它涉及端点发现、安全策略选择、证书验证、会话创建四个阶段。Demo 里的连接逻辑大致是这样// 创建应用配置ApplicationName 和 ApplicationUri 必须唯一 var appConfig new ApplicationConfiguration() { ApplicationName MyOpcUaClient, ApplicationUri $urn:{Utils.GetHostName()}:MyOpcUaClient, ApplicationType ApplicationType.Client, SecurityConfiguration new SecurityConfiguration { // 自动接受不受信任的证书仅限开发环境 AutoAcceptUntrustedCertificates true, // 证书存储路径生产环境要改成受控目录 ApplicationCertificate new CertificateIdentifier { StoreType CertificateStoreType.Directory, StorePath pki/own, SubjectName CNMyOpcUaClient } }, ClientConfiguration new ClientConfiguration { DefaultSessionTimeout 60000 } }; // 验证配置并创建证书 await appConfig.Validate(ApplicationType.Client); await appConfig.SecurityConfiguration.ApplicationCertificate.FindOrCreateCertificate(); // 发现端点选择安全策略 var endpointDescription CoreClientUtils.SelectEndpoint( opc.tcp://192.168.1.100:4840, useSecurity: false, discoverTimeout: 15000); // 创建会话 var endpointConfiguration EndpointConfiguration.Create(appConfig); var endpoint new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); var session await Session.Create( appConfig, endpoint, updateBeforeConnect: false, sessionName: MySession, sessionTimeout: 60000, identity: new UserIdentity(), preferredLocales: null);这段代码里有几个参数值得单独拎出来说。DefaultSessionTimeout设的是 60000 毫秒也就是 60 秒这是会话在没有活动的情况下能保持的最长时间。现场如果网络抖动频繁这个值可以适当放大到 120000但不要超过服务端配置的上限否则服务端会直接拒绝。SelectEndpoint的useSecurity参数在开发阶段设成false能省去证书配置的麻烦但上生产必须改成true并配置好安全策略。Session.Create里的updateBeforeConnect如果设成true每次连接前都会重新发现端点适合服务端地址可能变化的场景但会增加连接耗时。连接建立之后建议立刻做一件事读取服务端的ServerStatus节点确认服务端状态正常。这个动作花不了多少时间但能帮你排除掉「连上了但服务端其实有问题」的情况。3. 读写操作与订阅机制从单点读取到批量监听3.1 同步读写与异步读写的选择OPC UA 的读写操作分同步和异步两套 API。Demo 里两种都给了示例但实际项目里我强烈建议用异步版本。原因很直接同步调用在服务端响应慢的时候会阻塞当前线程如果你的上位机 UI 线程被阻塞界面直接卡死。异步版本虽然写起来多几个await但不会出现这个问题。先看单点读取// 同步读取不推荐在 UI 线程使用 var readValue session.ReadValue(ns2;sDevice1.Temperature); Console.WriteLine($温度: {readValue.Value}); // 异步读取推荐方式 var readValueAsync await session.ReadValueAsync(ns2;sDevice1.Temperature); Console.WriteLine($温度: {readValueAsync.Value});ReadValue返回的是一个DataValue对象里面除了Value还有StatusCode和SourceTimestamp。很多人只看Value忽略了StatusCode结果服务端返回了BadNodeIdUnknown还在那纳闷为什么值是 null。正确的做法是每次读取后先判断StatusCode是否为Good。批量读取用Read方法传入一个ReadValueIdCollection// 批量读取多个节点减少网络往返 var nodesToRead new ReadValueIdCollection { new ReadValueId { NodeId ns2;sDevice1.Temperature, AttributeId Attributes.Value }, new ReadValueId { NodeId ns2;sDevice1.Pressure, AttributeId Attributes.Value }, new ReadValueId { NodeId ns2;sDevice1.Status, AttributeId Attributes.Value } }; session.Read(null, 0, TimestampsToReturn.Both, nodesToRead, out var results, out var errors);批量读取的价值在于减少网络往返次数。三个节点分三次读就是三次网络请求批量读一次请求就搞定。在节点数量多、网络延迟高的场景下这个差距非常明显。写入操作相对简单但要注意数据类型匹配// 写入单个节点注意值的数据类型要和节点定义一致 var writeValue new WriteValue { NodeId ns2;sDevice1.SetPoint, AttributeId Attributes.Value, Value new DataValue(new Variant(25.5)) }; session.Write(null, new WriteValueCollection { writeValue }, out var writeResults, out var writeErrors);写入失败最常见的原因是数据类型不匹配。比如节点定义的是Int32你写了个Double服务端会返回BadTypeMismatch。解决办法是先读一次节点的DataType属性确认类型后再写。3.2 订阅与心跳监听MonitoredItem 的参数怎么调订阅是 OPC UA 最实用的功能之一它让服务端在数据变化时主动推送而不是客户端不停地轮询。Demo 里的订阅逻辑分三步创建 Subscription、添加 MonitoredItem、处理通知事件。// 创建订阅PublishingInterval 是服务端推送的最小间隔 var subscription new Subscription(session.DefaultSubscription) { PublishingInterval 1000, // 服务端每 1000ms 检查一次变化 KeepAliveCount 10, // 10 个周期无变化则发送心跳 LifetimeCount 30, // 30 个周期无响应则订阅失效 MaxNotificationsPerPublish 1000, PublishingEnabled true, Priority 0 }; session.AddSubscription(subscription); subscription.Create(); // 添加监控项 var monitoredItem new MonitoredItem(subscription.DefaultItem) { DisplayName Temperature, StartNodeId ns2;sDevice1.Temperature, AttributeId Attributes.Value, SamplingInterval 500, // 客户端采样间隔 QueueSize 10, // 队列大小防止通知丢失 DiscardOldest true // 队列满时丢弃最旧的数据 }; monitoredItem.Notification OnNotification; // 绑定通知回调 subscription.AddItem(monitoredItem); subscription.ApplyChanges();这里面的参数关系要理清楚。PublishingInterval是服务端层面的推送周期SamplingInterval是客户端层面的采样周期。如果SamplingInterval大于PublishingInterval实际推送频率会受限于采样间隔。QueueSize设成 10 意味着最多缓存 10 条未处理的通知超过之后根据DiscardOldest决定丢弃策略。现场调试时如果发现数据丢包优先检查QueueSize是不是太小。心跳监听是很多人忽略的一环。OPC UA 的KeepAliveCount机制本质上就是心跳如果服务端在KeepAliveCount个PublishingInterval内没有数据变化它会发送一个空的通知包证明自己还活着。客户端可以通过Subscription的KeepAlive事件来监听这个心跳// 监听订阅的心跳事件判断服务端是否存活 subscription.KeepAlive (sender, args) { if (args.Status.Error) { Console.WriteLine($心跳异常: {args.Status.Code}); // 触发重连逻辑 } };这个回调是判断连接健康度的关键。如果连续多个心跳周期都没有收到 KeepAlive基本可以断定连接已经断了需要走重连流程。4. 断开重连与异常处理那些让你半夜爬起来的问题4.1 断开的正确姿势与重连策略断开连接看起来简单但做错了会留下僵尸会话。正确的断开流程是先删除订阅再关闭会话最后释放通道。// 正确的断开顺序 if (subscription ! null) { subscription.Delete(true); // 删除订阅true 表示删除监控项 session.RemoveSubscription(subscription); } session.Close(); // 关闭会话 session.Dispose(); // 释放资源如果直接调session.Dispose()而不先删订阅服务端可能在一段时间内仍然认为会话活跃导致资源泄漏。现场如果频繁重连服务端的会话数会越积越多最终拒绝新连接。重连策略我一般用指数退避第一次断线后等 1 秒重连失败等 2 秒再失败等 4 秒上限 30 秒。这样既能快速恢复又不会在服务端还没准备好的时候疯狂重试。// 指数退避重连 private async Task ReconnectWithBackoff(Session session) { int delay 1000; while (true) { try { await session.ReconnectAsync(); Console.WriteLine(重连成功); break; } catch (Exception ex) { Console.WriteLine($重连失败: {ex.Message}{delay}ms 后重试); await Task.Delay(delay); delay Math.Min(delay * 2, 30000); // 上限 30 秒 } } }4.2 常见异常与排查思路OPC UA 的异常信息有时候比较隐晦这里列几个我踩过的坑。BadSessionClosed通常意味着会话被服务端主动关闭了原因可能是会话超时、服务端重启、或者并发会话数超限。BadSecureChannelClosed是安全通道断了多半是网络问题或者证书过期。BadTimeout是请求超时检查网络延迟和服务端负载。BadNotConnected是客户端根本没连上检查端点地址和端口。排查的时候我习惯先开 OPC UA 的日志追踪把日志级别调到Debug能看到完整的请求响应过程。很多问题看日志比猜快得多。5. 避坑指南五条血泪经验5.1 证书问题AutoAcceptUntrustedCertificates 不是万能药现象开发环境连得好好的部署到现场就连不上报BadCertificateUntrusted。原因开发时开了AutoAcceptUntrustedCertificates true客户端自动接受了服务端的自签名证书。但生产环境的服务端证书是 CA 签发的客户端没有把 CA 证书加入信任列表验证就失败了。解决把服务端的 CA 证书导入客户端的信任存储或者把服务端证书的指纹加入白名单。不要在生产环境开AutoAcceptUntrustedCertificates这是个安全漏洞。5.2 会话超时DefaultSessionTimeout 设太大反而容易断现象把DefaultSessionTimeout设成 3000005 分钟结果会话反而更频繁地断。原因服务端有自己的MaxSessionTimeout限制客户端设的值超过服务端上限时服务端会用自己的上限值但客户端不知道双方对超时时间的认知不一致导致会话被服务端提前关闭。解决先读服务端的ServerConfiguration节点拿到MaxSessionTimeout的实际值客户端设置不要超过它。5.3 订阅丢数据QueueSize 和 SamplingInterval 的配合现象订阅的数据偶尔丢几条尤其是数据变化频繁的时候。原因QueueSize设得太小数据变化速度超过消费速度时队列溢出旧数据被丢弃。解决把QueueSize调大同时根据数据变化频率调整SamplingInterval。如果数据变化非常快考虑用DiscardOldest false保证不丢数据但可能增加延迟。5.4 重连后订阅失效没有重新创建 MonitoredItem现象断线重连后连接恢复了但订阅的数据不再推送。原因重连后Session对象是新的原来的Subscription和MonitoredItem已经失效需要重新创建。解决在重连成功的回调里重新执行订阅创建和监控项添加的逻辑。可以把这部分封装成一个SetupSubscriptions方法连接和重连都调它。5.5 心跳误判KeepAlive 事件不等于连接正常现象KeepAlive事件正常触发但实际读写操作全部超时。原因KeepAlive只代表订阅通道正常不代表整个会话的读写通道正常。有时候安全通道出了问题订阅还能收到心跳但读写请求已经发不出去了。解决不要只依赖KeepAlive判断连接状态定期做一次轻量级的读操作比如读ServerStatus的CurrentTime节点读写成功才算真正健康。6. 进阶技巧用批量读取和订阅组合做高效数据采集跑通 Demo 之后下一步就是把它用到实际项目里。我一般会做一个组合对变化频率低的配置类数据用批量读取对变化频率高的实时数据用订阅。这样既能保证实时性又不会因为订阅太多节点导致服务端压力过大。具体做法是先批量读取所有节点的当前值做一次全量初始化然后只对需要实时监控的节点创建订阅。订阅的PublishingInterval根据数据变化频率来定温度这类慢变量设 2000ms 就够了状态位这类快变量设 200ms。验证连接健康度的时候我习惯加一个定时器每 30 秒读一次ServerStatus的CurrentTime如果连续三次读取失败就触发重连。这个逻辑比单纯依赖KeepAlive可靠得多。还有一个细节Session对象的ReconnectAsync方法在重连成功后会保留原来的订阅但前提是服务端还保留着会话状态。如果服务端重启了会话状态丢失就必须重新创建订阅。所以重连逻辑里要判断一下如果ReconnectAsync之后订阅的Created属性为false就重新走一遍订阅创建流程。从那以后我每次做 OPC UA 项目都会先把连接、读写、订阅、心跳、重连这五件事的日志全部打开跑够 24 小时再上现场。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑