☰
C# .NET Core 实现 OPC UA 客户端:连接西门子 PLC 与订阅实战
2026/10/8 4:29:55 网站建设 项目流程

简介:这份资源面向工业自动化与物联网方向的C#开发者,提供一套基于.NET Core的OPC UA开发环境,覆盖OPC UA规范1.03版,适合希望快速上手或验证客户端与服务端通信的初中级工程师。压缩包共195个文件,以179个dll动态库为核心,辅以4个exe可执行程序、4个config配置文件和4个xml说明文件,整体约15.06MB,其中Opc.Ua.Core.dll与BouncyCastle.Crypto.dll承担协议栈与加密通信职责,config文件则对应示例程序的运行参数。资源内含SampleClient、BoilerClient等客户端示例,以及BoilerServer、ReferenceServer等服务端示例,可分别演示数据读写、变量订阅、方法调用与锅炉模拟场景,帮助读者理解OPC UA的信息模型、安全模型与传输层机制。目前已有557人学习下载,可作为跨平台OPC UA应用开发的参考起点。

1. 从一台西门子 PLC 说起:C# 在 .NET Core 上跑通 OPC UA 客户端到底难在哪

车间里一台 S7-1500 已经开了 OPC UA 服务端,上位机却还在用老掉牙的 WinCC 或者某个只认 Windows 的 DLL 去读数据。你想用 C# 写一个跨平台的上位机,跑在 .NET Core 上,最好还能丢进 Linux 工控机里长期运行——这个念头一起,坑就来了。标题里的 opc_C#OPCUA_.netcore_opcua客户端_覆盖1.03版_OPCUA,说的就是这么一件事:用 C# 在 .NET Core 环境下实现一个 OPC UA 客户端,覆盖到 1.03 版协议规范,能连 PLC、能读点位、能订阅变化。它解决的是「把设备数据稳定拿进自己的程序」这个最朴素也最要命的需求,适合做上位机、SCADA、MES 数据采集、设备状态监控的工程师。难点不在协议本身,而在 .NET Core 的生态里选哪个库、证书怎么过、会话怎么保活、断线怎么重连——这些才是真正让人翻车的地方。

2. 选型先定生死:.NET Core 下能用的 OPC UA 客户端库怎么挑

2.1 为什么不是随便找个 NuGet 包就能用

OPC UA 不是 HTTP,它是一套完整的二进制协议栈,包含传输层、安全通道、会话、订阅、方法调用等一整套规范。在 .NET Core 上,能用的客户端库其实就那么几个方向:一是 OPC Foundation 官方那套基于 .NET Standard 的栈,二是社区维护的轻量封装,三是某些商业库的免费额度版。官方栈最完整,覆盖到 1.03 甚至更高版本的规范,但 API 偏底层,证书和会话管理要自己写不少代码;社区封装上手快,但遇到复杂类型或者安全策略协商时容易卡住。

我一般会先确认三件事:目标 PLC 或服务端支持哪些安全策略(None、Basic256Sha256、Aes128Sha256RsaOaep 等)、是否需要订阅而不是轮询、是否要跨平台跑在 Linux 上。如果这三点里有一项是硬需求,官方栈基本是唯一稳妥选择。选型时别只看 GitHub 星数,要看它最近一次提交是不是还在跟进 .NET 6/7/8 的兼容性,很多包在 .NET Framework 下能用,一到 .NET Core 就报平台不支持。

2.2 用 NuGet 把最小客户端骨架搭起来

先建一个 .NET Core 控制台项目,把官方栈的核心包引进来。下面这条命令是搭骨架的第一步,注意包名和版本要跟你实际用的 .NET 版本匹配,别硬抄。

dotnet new console -n OpcUaClientDemo cd OpcUaClientDemo dotnet add package OPCFoundation.NetStandard.Opc.Ua.Client dotnet add package OPCFoundation.NetStandard.Opc.Ua.Configuration

这两条包引用分别负责客户端会话和配置加载。装完之后,项目文件里会出现对应的 PackageReference。这里有个细节:如果你只引 Client 不引 Configuration,编译时不会报错,但运行到加载应用配置那一步会直接抛异常,提示找不到配置类型。所以两个一起加,别省。

2.3 连接之前先把应用配置和证书理清楚

OPC UA 的安全通道建立依赖应用实例证书。在 .NET Core 上,证书默认会生成到用户目录下的一个 pki 文件夹里。第一次运行时如果目录不存在,程序会自己建,但在 Linux 上要注意运行账户对那个目录有没有写权限,否则会卡在证书生成那一步,日志里只给一句很模糊的「无法创建应用实例证书」。

var config = new ApplicationConfiguration() { ApplicationName = "OpcUaClientDemo", ApplicationType = ApplicationType.Client, SecurityConfiguration = new SecurityConfiguration { AutoAcceptUntrustedCertificates = true, // 调试阶段临时用,生产环境必须关掉 ApplicationCertificate = new CertificateIdentifier { StoreType = CertificateStoreType.Directory, StorePath = "pki/own", SubjectName = "CN=OpcUaClientDemo" }, TrustedIssuerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = "pki/issuer" }, TrustedPeerCertificates = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = "pki/trusted" }, RejectedCertificateStore = new CertificateTrustList { StoreType = CertificateStoreType.Directory, StorePath = "pki/rejected" } }, TransportConfigurations = new TransportConfigurationCollection(), TransportQuotas = new TransportQuotas { OperationTimeout = 15000 }, ClientConfiguration = new ClientConfiguration { DefaultSessionTimeout = 60000 } }; await config.Validate(ApplicationType.Client);

这段代码做了三件事:定义应用类型为客户端、指定证书存储路径、设置操作超时和会话超时。AutoAcceptUntrustedCertificates在调试时能省掉手动信任服务端证书的麻烦,但上线前一定要改成 false,否则等于把安全通道的校验关了。OperationTimeout默认值偏短,遇到网络抖动大的现场,15 秒是个比较稳的起点。DefaultSessionTimeout决定会话多久没心跳就断,后面做保活时要跟这个值对齐。

3. 连上只是开始:会话、订阅与数据读取的落地写法

3.1 建立会话并拿到第一个点位值

会话建立是 OPC UA 客户端最核心的一步。下面这段代码把配置、端点选择和会话创建串起来,注意端点描述里的安全策略要跟服务端实际开启的一致,否则会报「找不到匹配的端点」。

var endpointDescription = CoreClientUtils.SelectEndpoint("opc.tcp://192.168.1.10:4840", useSecurity: false); var endpointConfiguration = EndpointConfiguration.Create(config); var endpoint = new ConfiguredEndpoint(null, endpointDescription, endpointConfiguration); var session = await Session.Create( config, endpoint, updateBeforeConnect: false, sessionName: "DemoSession", sessionTimeout: 60000, identity: new UserIdentity(new AnonymousIdentityToken()), preferredLocales: null ); var readValue = session.ReadValue("ns=3;s=\"DB1\".\"Temperature\""); Console.WriteLine($"温度值: {readValue.Value}");

SelectEndpoint的第二个参数useSecurity设为 false 表示走无安全策略,适合内网调试;生产环境要改成 true 并传入证书。Session.Create里的identity决定用什么身份连服务端,匿名、用户名密码、证书三种方式按现场配置选。ReadValue里的节点 ID 写法是ns=命名空间索引;s=节点标识,不同 PLC 的命名空间索引不一样,西门子一般是 3,但别死记,用 UaExpert 先连上看一眼最稳。

3.2 用订阅替代轮询:把变化数据推回来

轮询读点位在点位数少时还行,一旦上百个点,网络和 PLC 都扛不住。OPC UA 的订阅机制才是正路。下面这段代码创建一个订阅,把几个点位加进去,并注册回调。

var subscription = new Subscription(session.DefaultSubscription) { PublishingInterval = 1000, // 服务端推送周期,单位毫秒 KeepAliveCount = 10, // 多少次发布无变化后发心跳 LifetimeCount = 30, // 多少次发布无订阅者响应后删除订阅 MaxNotificationsPerPublish = 1000, PublishingEnabled = true, Priority = 0 }; session.AddSubscription(subscription); await subscription.CreateAsync(); var monitoredItem = new MonitoredItem(subscription.DefaultItem) { DisplayName = "Temperature", StartNodeId = "ns=3;s=\"DB1\".\"Temperature\"", AttributeId = Attributes.Value, SamplingInterval = 500, // 采样周期,要小于等于发布周期 QueueSize = 10, DiscardOldest = true }; monitoredItem.Notification += (item, args) => { if (args.NotificationValue is MonitoredItemNotification notification) { Console.WriteLine($"变化: {notification.Value.Value} 时间: {notification.Value.SourceTimestamp}"); } }; subscription.AddItem(monitoredItem); await subscription.ApplyChangesAsync();

PublishingInterval是服务端往客户端推的周期,SamplingInterval是服务端去 PLC 采样的周期,后者必须小于等于前者,否则数据会丢。QueueSize决定采样快于发布时缓存多少个值,DiscardOldest为 true 表示缓存满时丢最旧的。回调里拿到的SourceTimestamp是 PLC 侧的时间戳,不是客户端收到的时间,做时序分析时要用这个。订阅创建后如果一直收不到通知,先检查PublishingEnabled是不是 true,再看服务端有没有把这个节点标记为可订阅。

3.3 断线重连与会话保活:别让程序悄悄死掉

工业现场网络抖动是常态,会话断了不重连,程序看起来还在跑,数据早就不更新了。下面这段是重连逻辑的核心骨架。

session.KeepAlive += async (s, e) => { if (e.Status != null && ServiceResult.IsBad(e.Status)) { Console.WriteLine($"会话异常: {e.Status}"); try { await session.ReconnectAsync(); Console.WriteLine("重连成功"); } catch (Exception ex) { Console.WriteLine($"重连失败: {ex.Message}"); // 这里可以加退避重试,别用死循环硬刷 } } };

KeepAlive事件在每次心跳时触发,e.Status为坏值时说明会话已经不可用。ReconnectAsync会尝试用原参数重建会话,但订阅不会自动恢复,重连成功后要重新创建订阅或者调用订阅的RecreateAsync。我一般会在重连成功后加一个状态标志,让上层逻辑知道数据可能断了一段,别拿旧值当实时值用。退避重试建议用 1 秒、2 秒、5 秒、10 秒这样的间隔,别一失败就疯狂重连,会把服务端拖垮。

4. 避坑与排查:那些让 OPC UA 客户端翻车的细节

4.1 证书报错「BadCertificateUntrusted」怎么处理

现象是连接时直接抛异常,提示服务端证书不受信任。原因是客户端第一次连某个服务端时,服务端证书不在信任列表里。解决方式有两种:调试阶段把AutoAcceptUntrustedCertificates设为 true,让客户端自动接受;生产环境则要把服务端证书导出,放进客户端的pki/trusted目录,然后重启客户端。注意别把服务端证书丢进pki/issuer,那个目录是放签发者证书的,放错了不生效。

4.2 订阅收不到数据但读点位正常

现象是ReadValue能拿到值,订阅回调一直不触发。常见原因是SamplingInterval设得比PublishingInterval大,服务端采样比发布还慢,自然推不出变化。另一个原因是节点的AttributeId没设成Attributes.Value,默认可能是别的属性。还有一种情况是服务端对订阅数量有限制,超过上限后新订阅被静默拒绝,这时要看服务端日志或者减少订阅数。

4.3 在 Linux 上跑报「PlatformNotSupportedException」

现象是同一个项目在 Windows 上正常,丢到 Linux 上启动就崩。原因是某些 OPC UA 库依赖 Windows 特有的证书存储或者加密 API。解决方式是确认用的包是不是纯 .NET Standard 实现,证书存储类型改成Directory而不是X509Store。另外 Linux 上要装好libssl相关依赖,否则安全通道协商会失败,报的错往往很隐晦,只说握手失败。

4.4 会话频繁断开但网络看着正常

现象是会话每隔几分钟就断一次,重连后过一会又断。原因通常是DefaultSessionTimeout设得太短,而客户端没有及时发心跳。OPC UA 会话靠客户端定期发Publish请求维持,如果订阅的PublishingInterval比会话超时还大,服务端会认为客户端失联。解决方式是让PublishingInterval小于DefaultSessionTimeout的三分之一,并确保KeepAlive事件里正确处理坏状态。

4.5 节点 ID 写错却报「BadNodeIdUnknown」

现象是读某个点位时报节点不存在,但用 UaExpert 能看到这个点。原因是命名空间索引在不同会话里可能不一样,尤其是服务端重启后。解决方式是用session.NamespaceUris拿到实际的命名空间数组,按 URI 去匹配索引,而不是硬编码ns=3。西门子 PLC 的命名空间 URI 一般是http://www.siemens.com/s7opcua这类,匹配到之后再拼节点标识。

5. 把客户端做成能长期跑的服务:几个我踩过坑才定下来的习惯

先说一个进阶用法:把 OPC UA 客户端封装成一个后台服务,用IHostedService托管,配合CancellationToken做优雅退出。这样丢进 Linux 的 systemd 里就能开机自启,不用挂着控制台窗口。封装时把会话、订阅、重连逻辑收在一个类里,对外只暴露「连接状态」和「数据变化」两个事件,上层业务不碰协议细节。

验证客户端稳不稳,我一般会做三件事:一是拔网线三十秒再插回去,看能不能自动重连并恢复订阅;二是把服务端重启一次,看客户端会不会崩;三是连续跑七十二小时,看内存有没有持续上涨。内存涨通常是订阅回调里做了耗时操作或者事件没解绑,把回调里的逻辑丢到队列里异步处理,别在回调里直接写数据库。

参数上有一张我常用的对照表,现场调的时候直接改这几个值:

参数调试值生产建议值说明
OperationTimeout1500010000~20000网络差取大,内网取小
DefaultSessionTimeout60000120000要大于 PublishingInterval 的三倍
PublishingInterval1000500~2000按数据变化频率调
SamplingInterval500等于或略小于 PublishingInterval别大于发布周期
QueueSize105~20变化快就加大
KeepAliveCount1010~30太大断线发现慢

最后说个习惯:每次改完连接参数,先用 UaExpert 连一遍确认服务端侧没问题,再跑自己的客户端。这样能把「服务端配置问题」和「客户端代码问题」分开,省掉大量来回猜的时间。OPC UA 这东西,协议本身不玄学,玄学的是现场网络和证书,把这两样摸清楚,剩下的就是写代码的功夫。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询