十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

C#上位机接入OPC UA实战:连接、读写、订阅与断线重连全解析

C#上位机接入OPC UA实战:连接、读写、订阅与断线重连全解析 简介面向工业自动化与.NET开发者的OPC UA C#客户端示例包围绕C#与PLC通信、数据采集这一核心场景帮助开发者快速理解OPC UA协议中的服务调用、节点浏览与订阅机制。压缩包共135个文件其中55个C#源码文件构成主要示例辅以项目工程文件sln/csproj、配置与资源文件、少量DLL和可执行程序整体体积仅1.61MB目录结构清晰便于按模块阅读与复用。已有3584人学习下载。示例覆盖了客户端初始化、服务器连接与认证、浏览节点定位数据源、创建订阅接收变化通知、读写PLC变量、异常处理及安全断开连接等完整流程并演示了async/await异步模型在实时数据采集中的用法。通过研读源码可以掌握UA-.NETStandard等开源库的典型调用方式理解OPC UA对象模型和信息交换逻辑适合希望在.NET项目中集成OPC UA通信能力、提升工业软件开发效率的工程师参考。 做上位机这些年我接手过的项目里十个有八个绕不开设备数据采集。早些年各家设备各说各话西门子走S7三菱走MC罗克韦尔走CIP一个上位机里塞满各种通讯驱动光维护这些驱动就能耗掉不少精力。后来逐渐统一到OPC UA这条路上一个C#客户端通吃各种设备代码量少了一大截。今天这篇就从一个能直接运行的OPC UA C#示例说起把连接、读、写、订阅、断线重连、证书处理这些最常用的功能全部跑通。适合正在做C#上位机开发、要对接PLC或MES的工程师也适合刚接触OPC UA、想知道从哪下手的同学。1. 项目思路拆解C#接入OPC UA到底在解决什么问题1.1 核心需求统一工业通讯层先说我实际遇到过的一个典型场景。一条产线上有PLC、有机器人、有视觉相机还有几台老设备每台设备的通讯方式都不一样有的是Modbus TCP有的是自定义TCP报文有的干脆给一个厂家封好的DLL。以前的做法是每台设备写一个驱动类上位机里装一堆驱动还要处理各种断连、重连、超时异常。后来客户要求上MES所有设备数据要统一采集这时候OPC UA的优势就体现出来了——它在语义层定义了统一的地址空间和数据模型现场的PLC、传感器、相机只要都暴露出OPC UA Server上位机只需要写一个Client就能全部搞定。从技术角度看OPC UA把数据怎么描述、怎么安全传输、怎么通知变化这三件事都标准化了。C#所在的.NET生态又天然适合Windows上位机开发两者搭配能省掉大量从零造轮子的工作。很多时候我们纠结“用Modbus还是Socket”或者“怎么跟VisionMaster通讯”本质都是在找一个稳定、好维护的通讯层而OPC UA恰好就是这个答案。1.2 方案对比为什么不是Modbus TCP或裸Socket这里给个直观的对比可以按这个思路选型方案数据模型部署与安全适用场景Modbus TCP简单寄存器地址无加密几乎无安全小型PLC、传感器快速采集裸Socket/TCP完全自定义全部自己做已有固定协议的老设备OPC DACOM/DCOMWindows域环境部署麻烦老项目兼容OPC UA面向对象节点树证书、加密、审计跨平台、MES对接、新项目首选我个人的选择标准是如果只是从一台PLC读几十个寄存器协议改动不大Modbus TCP够用但如果设备来源杂、后续要上MES或者数据要在多个系统间共享就直接上OPC UA省得返工。热词里有人问“海康相机VisionMaster与C#上位机通讯用什么协议比较好”如果只是视觉软件和上位机之间传结果用SDK或TCP都行但要让PLC联动执行分拣或报警用OPC UA把检测结果写成节点各方统一消费同一棵树上的数据效率会高很多。1.3 为什么用C#而不是C或JavaC#代码可读性好开发效率高跟Windows下的调试工具配合得也顺。拿C比省心很多不用操心指针、内存泄漏拿Java比Windows GUI、串口网口编程、跟原生DLL交互都更顺手。这两年.NET一直在更新.NET 6/8下写控制台程序、Windows服务、WinForm/WPF开发都很快。工业上位机应用层开发如果不涉及底层驱动C#几乎是最合适的选择。我自己从C转C#之后同类项目开发周期差不多缩短了三分之一尤其是处理字符串、JSON、界面绑定这些杂活时C#的体验要好太多。2. 手写代码前先把这几个概念搞明白2.1 地址空间服务器侧的信息树OPC UA服务器不是用IP加寄存器地址来定位数据的它维护了一棵“信息树”。树上每个东西都叫节点节点下面可以挂变量、方法、对象这些内容。要读数据就得先知道目标节点的NodeId常见格式是ns2;sTag1这种ns是命名空间索引s是字符串标识。很多人在读取时报BadNodeIdUnknown十有八九是NodeId写错了或者命名空间不对。所以我强烈建议正式写代码前先用Prosys OPC UA Browser这类工具连到服务器上看一看树结构确认节点的NodeId和数据类型后再动手。这比在代码里反复猜、反复试错高效得多。Prosys OPC UA Browser是免费的连接后左侧就是节点树点开就能看到每个节点的属性、值、数据类型还可以临时写值测试服务器是否允许写入。2.2 通讯服务会话、读写、订阅OPC UA客户端和服务器之间先建立一个会话可以理解成登录登录后可以执行浏览、读取、写入、调用方法这些服务。如果数据需要持续监测再创建一个订阅订阅里挂上监视项服务器会在采样周期内检测变量变化并自动推送给客户端不用客户端高频轮询。我用一个仓库比喻来理解服务器就是一个仓库会话是进门通行证读就是看货架上的标签写就是把标签换掉订阅则是雇了个员工货架一变他就跑过来告诉你。这个模型兼顾了实时性和带宽比Modbus那种轮询方式优雅得多。轮询是客户端反复问“变了吗变了吗”订阅是服务器主动说“变了新值是xxx”现场点位一多差别就非常明显。2.3 UA-.NETStandard库与环境准备C#端最成熟的OPC UA库是官方生态的UA-.NETStandard在NuGet上的包名是OPCFoundation.NetStandard.Opc.Ua.Client主要用来写客户端。另外还有一个OPCFoundation.NetStandard.Opc.Ua.Core可以用于搭建服务器端。市面上第三方库也有很成熟的但商业授权麻烦我推荐直接用官方库免费开源功能覆盖日常开发完全够。开发环境建议这样准备Visual Studio 2022或VS Code.NET 6或.NET 8 SDKNuGet包OPCFoundation.NetStandard.Opc.Ua.Client模拟服务器Prosys OPC UA Simulation Server方便本地调试辅助工具Prosys OPC UA Browser浏览地址空间如果没有现成的PLC设备用模拟服务器就能跑通全部示例代码。Prosys的模拟服务器自带一批标签节点比如Tag1、Tag2数据类型和读写属性都能配置非常适合学习阶段使用。3. 实操把一个OPC UA C#客户端从零跑起来3.1 最小连接代码先创建一个控制台项目并安装NuGet包dotnet new console -n OpcUaClientDemo cd OpcUaClientDemo dotnet add package OPCFoundation.NetStandard.Opc.Ua.Client --version 1.5.374.522然后准备一个Config.xml配置文件放在项目根目录并设置复制到输出目录。UA-.NETStandard需要一个ApplicationConfiguration来描述客户端自身的证书和安全配置。下面的模板可以直接抄?xml version1.0 encodingutf-8? ApplicationConfiguration xmlnshttp://opcfoundation.org/UA/2008/02/Types.xsd ApplicationNameOpcUaClientDemo/ApplicationName ApplicationUriurn:localhost:OpcUaClientDemo/ApplicationUri ApplicationTypeClient/ApplicationType SecurityConfiguration ApplicationCertificate StoreTypeDirectory/StoreType StorePathpki/own/StorePath SubjectNameCNOpcUaClientDemo/SubjectName /ApplicationCertificate TrustedPeerCertificates StoreTypeDirectory/StoreType StorePathpki/trustedPeer/StorePath /TrustedPeerCertificates /SecurityConfiguration TransportConfigurations / TransportQuotas MaxMessageSize4194304/MaxMessageSize /TransportQuotas ClientConfiguration DefaultSessionTimeout60000/DefaultSessionTimeout /ClientConfiguration /ApplicationConfiguration注意pki路径这里要用正斜杠而不是反斜杠Windows下容易在这踩坑。接下来写连接代码using Opc.Ua; using Opc.Ua.Configuration; using Opc.Ua.Client; var application new ApplicationInstance { ApplicationName OpcUaClientDemo, ApplicationType ApplicationType.Client }; var config await application.LoadApplicationConfiguration(Config.xml, false); bool certOk await application.CheckApplicationInstanceCertificate(false, CertificateFactory.DefaultLifeTime); var endpointUrl opc.tcp://127.0.0.1:4840; var endpointDescription CoreClientUtils.SelectEndpoint(config, endpointUrl, true); var endpointConfiguration EndpointConfiguration.Create(config); var endpoint new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); using var session await Session.Create( config, endpoint, false, CSharpClientSession, 60000, new UserIdentity(new AnonymousIdentityToken()), null); Console.WriteLine(连接成功); Console.WriteLine($服务器: {session.Endpoint.EndpointUrl});解释一下关键参数。CheckApplicationInstanceCertificate是确认客户端证书存在第一次运行会自动生成到pki/own目录。SelectEndpoint会解析服务器地址并筛选安全策略。Session.Create里最后一个null是事件回调用来监听KeepAlive后面断线重连会用到。这里用的匿名身份生产环境建议用用户名密码或证书认证。3.2 浏览节点并读取变量连接成功后先浏览一下服务器上的节点结构。核心是用Browse服务var browseDescription new BrowseDescription { NodeId ObjectIds.ObjectsFolder, BrowseDirection BrowseDirection.Forward, ReferenceTypeId ReferenceTypeIds.HierarchicalReferences, IncludeSubtypes true, NodeClassMask (uint)NodeClass.Variable | (uint)NodeClass.Object, ResultMask (uint)BrowseResultMask.All }; await session.Browse( null, null, 0, new BrowseDescriptionCollection { browseDescription }, out var browseResults, out _); foreach (var reference in browseResults[0].References) { Console.WriteLine(${reference.DisplayName} - {reference.NodeId}); }浏览结果会显示节点名和对应的NodeId把要监控的节点记下来然后读取变量值var nodeToRead new ReadValueId { NodeId new NodeId(ns2;sTag1), AttributeId Attributes.Value }; await session.Read( null, 0, TimestampsToReturn.Both, new ReadValueIdCollection { nodeToRead }, out var results, out var diagnostics); DataValue dataValue results[0].Value; Console.WriteLine($Tag1 当前值: {dataValue.Value});读取时建议把多个点位放在一个ReadValueIdCollection里批量读不要一次读一个。OPC UA的Read服务本身支持批量把同一批次的所有点位放在一个请求里能显著减少网络往返点位多的时候性能差距非常明显。3.3 写入变量写值的核心是Write服务写入值要包装成DataValue同时要带上状态码和时间戳var nodesToWrite new WriteValueCollection { new WriteValue { NodeId new NodeId(ns2;sTag2), AttributeId Attributes.Value, Value new DataValue { Value 88.6, StatusCode StatusCodes.Good, SourceTimestamp DateTime.UtcNow } } }; await session.Write(null, nodesToWrite, out var writeResults, out _); if (writeResults[0].Code StatusCodes.Good) { Console.WriteLine(写入成功); } else { Console.WriteLine($写入失败: {writeResults[0].Code}); }写入时注意两点。第一目标节点必须允许写很多服务器会把部分变量设成只读写的时候会返回BadWriteNotSupported。第二数据类型要匹配服务器节点是Double类型就不要传int否则会被拒绝。建议写之前先看节点属性里的DataType定义。3.4 用订阅模式接收数据变化轮询读写是基本用法但现场数据量大时高频轮询既不实时也浪费带宽。正确姿势是订阅模式var subscription new Subscription(session.DefaultSubscription) { PublishingInterval 1000, KeepAliveCount 5, LifetimeCount 10, PublishingEnabled true, MaxNotificationsPerPublish 100 }; session.AddSubscription(subscription); subscription.Create(); var monitoredItem new MonitoredItem(subscription.DefaultItem) { StartNodeId new NodeId(ns2;sTag1), SamplingInterval 100, QueueSize 10, DiscardOldest true }; monitoredItem.Notification (item, args) { if (args.NotificationValue is MonitoredItemNotification notification) { Console.WriteLine(${DateTime.Now:HH:mm:ss.fff} {item.StartNodeId}: {notification.Value.Value}); } }; subscription.AddItem(monitoredItem); subscription.ApplyChanges(); Console.WriteLine(按回车退出); Console.ReadLine();很多新手会混淆两个时间参数PublishingInterval是服务器的发布周期也就是服务器多久汇总一次通知发给客户端SamplingInterval是采样周期也就是多久检测一次变量是否变化。1000毫秒发布、100毫秒采样是现场比较常用的组合。QueueSize是通知队列大小DiscardOldesttrue表示队列满了优先丢弃旧数据、保留最新值这样即使偶发网络抖动也能保证客户端拿到的是最新状态。4. 现场实战断线重连、安全策略和性能优化4.1 断线重连逻辑工业现场网络必然会抖动PLC也可能重启OPC UA服务端会短暂断开生产级客户端必须有重连机制。思路是注册Session的KeepAlive事件检测到通讯异常或会话失效后通过SessionReconnectHandler尝试重连private static SessionReconnectHandler _reconnectHandler; private static void Session_KeepAlive(Session session, KeepAliveEventArgs e) { if (e.ServiceResult.Code StatusCodes.BadCommunicationError || e.ServiceResult.Code StatusCodes.BadSessionIdInvalid) { Console.WriteLine($连接异常: {e.ServiceResult}5秒后重连...); if (_reconnectHandler null) { _reconnectHandler new SessionReconnectHandler(); _reconnectHandler.BeginReconnect(session, 5000, ReconnectHandler_Complete); } } } private static void ReconnectHandler_Complete(object? sender, EventArgs e) { Console.WriteLine(重连完成); _reconnectHandler?.Dispose(); _reconnectHandler null; }注意重连成功后订阅可能已经失效必须重新创建订阅并挂载MonitoredItem。我最开始写重连逻辑时漏掉了这步结果连接恢复了但数据不再推送查了很久才发现是订阅没有重建。4.2 安全策略与证书管理OPC UA支持的安全策略有None、Basic256Sha256、Aes128_Sha256_RsaOaep等。None最常用但明文传输生产环境不推荐。Basic256Sha256是现在工业设备默认支持得比较多的安全性和兼容性平衡得不错。如果启用安全连接服务器证书需要加入本地可信列表。第一次连接报BadCertificateUntrusted基本就是证书还没做信任配置。临时调试时可以在ApplicationConfiguration里把AutoAcceptUntrustedCertificates设为true但千万别在生产环境这么干安全策略形同虚设。用户名密码认证的写法也很简单把Session.Create里的UserIdentity换掉就行var userIdentity new UserIdentity(admin, password);像西门子S7-1500这类设备配置OPC UA Server时可以直接设用户名密码这个UserIdentity就是对应的认证凭证。4.3 性能优化与日志定位点位多的时候批量读、批量写、一个订阅挂多个MonitoredItem这三招能解决大部分性能问题。Read和Write天然支持批量只要不是特殊业务需求不建议逐条调用。订阅也一样把同刷新频率的点位放在同一个Subscription里复用同一个发布周期既省资源又容易管理。日志方面UA-.NETStandard底层有日志接口调试时可以打开Utils.SetTraceLog(new ConsoleTraceListener(), TraceMasks.Error | TraceMasks.Information);证书握手问题、底层通讯错误都能在日志里看到。生产环境建议把日志写到文件按天滚动现场出问题时定位会轻松很多。4.4 常见问题速查表现象可能原因处理办法BadSecurityModeRejected客户端与服务器安全策略不匹配SelectEndpoint时指定可用的安全策略BadCertificateUntrusted服务器证书未受信任把服务器证书加入TrustedPeerCertificate目录BadNodeIdUnknownNodeId写错或命名空间不对用Prosys Browser查看真实NodeIdBadSessionIdInvalid会话已失效走重连逻辑重新建立Session写入失败节点只读或类型不匹配确认节点写权限、DataType订阅没有数据采样/发布间隔过大或订阅失效调整PublishingInterval、SamplingInterval重连后重建订阅这些是我实际调试中遇到频率最高的问题基本占了日常排障的90%以上。5. OPC UA在上位机生态里的延伸玩法5.1 与PLC和MES的交互最常规的组合是PLC作为OPC UA ServerC#上位机作为Client采集数据并下发控制命令。再往上MES系统通过上位机提供的接口或数据库中间表取数整个链路数据格式统一新增设备只需要改节点配置表上位机逻辑基本不用动。这种架构的好处是设备层变动被隔离在OPC UA地址空间里上层系统感知不到具体设备差异。5.2 与机器视觉、扫码枪的数据协同热词里有人问“海康相机VisionMaster与C#上位机通讯使用什么协议比较好”。视觉软件的SDK可以直接在C#里调用但如果你要的不只是图像结果而是要产线PLC根据检测结果做分拣或报警更合理的架构是C#上位机作为视觉系统和PLC之间的枢纽视觉软件通过SDK或网络协议把检测结果交给上位机上位机再用OPC UA把结果写到PLC的节点上。扫码枪同理读到的条码通过串口或网口到上位机上位机用OPC UA把条码写入服务器节点MES和追溯系统直接从同一棵信息树里读。我一直觉得OPC UA在上位机生态里不是某一个设备驱动而是整条产线数据的公共总线。理解了这一点很多协议选型的纠结自然就有了答案。这套代码我在工控机上跑了大半年连过西门子S7-1500、倍福PLC还有几个国产设备的数据点稳定性和可维护性都超出预期。如果同事问我OPC UA C#从哪下手我一般就建议先把连接、读、写、订阅这四件事跑通再补上断线重连就已经能覆盖大部分项目需求。后续想深入的话可以研究服务器端开发或者看看PubSub模式那是另一套玩法等有空再写写。本文还有配套的精品资源点击获取
返回列表