
简介这是一套基于C#的OPC UA客户端源码目标是解决西门子机床等设备在非标准认证、加密策略与私有数据模型下的连接难题同时兼容KepServer等常见OPC UA服务器适合工业通信开发者和自动化集成人员参考。压缩包共2000个文件、约38.89MB以300余个cs核心源文件为主体配合xml配置、h/cpp原生依赖、htm接口文档以及sln/csproj工程文件并附带可运行的SampleClient示例与UA-.NET-Legacy基础框架便于快速定位、编译和二次移植。已有531人学习。整套源码覆盖会话管理、订阅发布、安全策略等OPC UA关键机制并通过示例展示从服务器发现、节点读写到订阅通知的完整流程对于需要适配西门子机床私有命名空间、跨品牌OPC UA服务器并兼顾加密通讯与后期维护的开发者是一份具备较高工程价值的参考实现。1. 先聊清楚为什么“都是OPC UA”还会连不上先说一个我常被问到的问题为什么我用OPC UA客户端连KepServer一下子就通换了西门子的机床就报错翻来覆去研究半天也不通很多刚接触C#上位机开发的朋友以为OPC UA是一套“装好就能通”的协议但实际上它不是一个“产品”而是一整套规范。OPC UA全称是OPC Unified Architecture它把数据访问、历史数据、报警、方法调用这些能力抽象成了统一的服务接口同时又允许不同厂家在信息模型上自由发挥。打个比方OPC UA像是大家约定好普通话但每个厂家的“词典”和“口音”不一样。所以一套C# OPC UA客户端源码如果想要做到“既能连西门子机床的OPC UA服务器又能连KepServer这类通用网关软件”本质上不是把代码写得多么花哨而是要把它写得足够“标准”——协议栈标准、配置灵活、异常处理宽容。这篇文章我就把我实际用过的一套源码结构、踩过的坑、以及为什么这样设计的原因完整梳理一遍。它不是官方Demo的照搬而是贴合车间现场、贴合C#上位机开发实际节奏的一套方案适合正在做机床数据采集、产线设备监控或者MES对接的工程师参考。这套代码能满足哪些实际需求连接西门子数控系统比如Sinumerik 840D sl的OPC UA服务器读取主轴转速、进给倍率、当前程序名等实时数据连接安装了KepServer的工控机把PLC、仪表、变频器的数据统一送上来甚至因为OPC UA是跨平台、跨厂商的标准同一份代码换个配置就能连上其他支持OPC UA的服务器。后续无论做数据展示、报警推送还是数据库落盘都在这套基础上叠加。2. 选型与库的选择为什么推荐OPCFoundation的UA-.NETStandard2.1 市面上的C# OPC UA库怎么选C#体系中做OPC UA客户端主流选择其实就是两种一种是OPCFoundation官方的UA-.NETStandard库另一种是商业库或者国内封装的开源库。我的建议非常直接——优先用OPCFoundation官方库因为这是OPC基金会自己维护的协议版本的跟进速度最快而且底层兼容.NET Framework 4.6.2以上和.NET Core/.NET 5也就是说不管是老旧的工控机Windows 7系统还是新的Linux工控机都能跑。可能有人问为什么不用某些“开箱即用”的第三方库我也试过几个确实上手快但做到后面会发现问题很尴尬——有些库对OPC UA的复杂类型比如结构体、数组、扩展对象支持不完整遇到西门子机床返回的特定数据节点解析不出来有些库对安全策略支持不全配置了Basic256Sha256之后握手总是失败。说实话做工业数据采集稳定性比开发速度重要得多协议的底层你做不了主那就得选一个真正把协议做到位的库。2.2 工程结构和依赖我实际使用的工程结构也很简单核心就是一个类库项目再加上一个测试用的WinForm或控制台项目。主工程引用的NuGet包有这几个OPCFoundation.NetStandard.Opc.Ua.Core OPCFoundation.NetStandard.Opc.Ua.Client OPCFoundation.NetStandard.Opc.Ua.Configuration OPCFoundation.NetStandard.Opc.Ua.Security.Certificates至少需要Core和Client两个包自动会带上基础依赖。这里要提醒一下NuGet包的版本建议保持统一不要Core用旧版、Client用新版版本混用会出现一些非常诡异的“对象不支持指定方法”的异常。我一般直接锁定到同一个版本号比如1.4.372。除了包引用项目中还必须有ApplicationCertificate这个环节因为OPC UA客户端在连接服务器之前往往需要生成或者加载自己的应用证书。代码里这一步可以自动完成但目录结构要提前规划好Configuration/ Opc.Ua.Client.Config.xml // 客户端全局配置 pki/ own/certs/ // 客户端证书 trusted/ // 信任的服务器证书 rejected/ // 不信任的证书调试时查看这个pki目录是OPC UA安全模型里的核心目录很多“连不上”的问题最终都可以在rejected目录里找到线索。2.3 ApplicationConfiguration所有连接的前置条件基于UA-.NETStandard的客户端第一步永远是配置ApplicationConfiguration。很多人在这里随便写写结果后面报各种证书和端点错误。我建议把配置项拆成三个必填部分var config new ApplicationConfiguration { ApplicationName MyOpcUaClient, ApplicationUri urn:mycompany:myopcuaclient, ApplicationType ApplicationType.Client, SecurityConfiguration new SecurityConfiguration { ApplicationCertificate new CertificateIdentifier { StoreType CertificateStoreType.X509Store, StorePath CurrentUser\\My, SubjectName CNMyOpcUaClient, CCN, SShanghai }, TrustedPeerCertificates new CertificateTrustList { StoreType CertificateStoreType.Directory, StorePath Configuration\\pki\\trusted }, RejectedCertificateStore new CertificateTrustList { StoreType CertificateStoreType.Directory, StorePath Configuration\\pki\\rejected } } };ApplicationUri这个值很多新手不理解其实它就是客户端自己的“身份证号”最好用固定格式不要每次运行随机生成。服务器的信任列表会记录这个Uri你改了它服务器那边可能又要重新信任一次。SecurityConfiguration里指定了证书存储位置客户端证书放在Windows证书库里信任的服务器证书放在pki目录里。每当服务器证书变更代码第一次连接会提示“未信任”去pki/trusted里把证书拉进来或者让TrustedPeerCertificates指向一个共享网络目录这样多台客户端可以共用一份信任列表。配置完成后调用await config.CertificateValidator.UpdateCertificateAsync()初始化证书验证器再调用config.CertificateValidator.CertificateValidation事件做日志记录。这一步不是走流程它能帮你快速定位证书信任问题。3. 源码的核心骨架连接、浏览、读写、订阅3.1 创建会话连接这一步最考验细节创建会话是整个流程里最经典的“看似简单实则磨人”的环节。一个最小化的连接代码如下var endpoint new Uri(opc.tcp://192.168.1.100:4840); var selectedEndpoint CoreClientUtils.SelectEndpoint(config, endpoint.ToString(), useSecurity: true); var session await Session.Create( config, selectedEndpoint, updateBeforeConnect: false, checkDomain: false, sessionName: MySession, sessionTimeout: 60000, identity: new UserIdentity(new AnonymousIdentityToken()), preferredLocales: new[] { zh-CN, en-US } );这里有几个对工业现场特别重要的参数。useSecurity: true表示启用加密和签名但如果对方是老的服务器、只支持None安全策略就得把useSecurity改成false或者用ConfiguredEndpoint手动指定SecurityMode。我一般在配置文件里留一个开关让现场人员不用改代码就能切换安全模式。checkDomain: false是很多人的救星。OPC UA的安全模型里服务器证书的域名或IP必须和连接地址匹配但车间现场的计算机名经常乱改或者服务器配置时用了主机名、连接时用IP这种情况下握手会直接失败。我后面会专门讲这个“计算机名不再与OPC UA配置的计算机名称匹配”的问题这里先埋个伏笔。UserIdentity决定身份认证方式。KepServer和西门子机床默认都允许匿名但如果企业安全策略要求用户名密码就换成new UserIdentity(user, password)。还要注意Session.Create返回的是一个高层次的会话对象它内部自动维护了通道、安全令牌的续订不需要自己CloseAsync一个旧的再开新的这一点比老式的OPC DA舒服得多。3.2 读取与写入看清节点ID的本质连接上之后读数据用ReadValueAsyncvar nodeId new NodeId(ns7;sChannel1.Device1.Tag1); var value await session.ReadValueAsync(nodeId); double result (double)value.Value;这里最核心的概念就是NodeId。OPC UA不像PLC那样直接用地址DB100.DBD12而是用“命名空间标识”组合出来的节点路径。你看到ns7就是命名空间索引为7s后面的字符串是服务器内部定义的标识。KepServer的节点通常形如ns2;sChannel1.Device1.Tag1而西门子机床的节点往往是ns3;sSinumerik/...这种层级结构也有的会是数字IDvar nodeId new NodeId(ns3;i1002);想搞清楚这些节点ID最可靠的办法是先用UA Expert或者本库自带的Session.Browse浏览一遍服务器节点树而不是去猜。这也是我代码里必带浏览功能的原因——车间设备经常换工艺节点路径对不上时现场工程师用客户端自带的树形浏览一眼就能找到新路径根本不用回办公室改代码。写入的代码也类似var writeValue new WriteValue { NodeId new NodeId(ns2;sChannel1.Device1.WriteTag), AttributeId Attributes.Value, Value new DataValue(new Variant(123)) }; var result await session.WriteAsync(null, new[] { writeValue });注意写入前要确认这个节点允许写并且数据类型要和服务器端一致。一个非常常见的坑是服务器端变量是Int16你写成Int32OPC UA虽然会自动转换但某些严格的服务器会报BadTypeMismatch。所以保险起见写入之前先读一次拿到原始值类型再用Convert.ChangeType转成对应类型。3.3 订阅数据变化上位机刷新的正确姿势做上位机界面的人都知道轮询读几百个变量会导致CPU和网络占用飙升更科学的方案是订阅。OPC UA的订阅模型核心是两个对象Subscription和MonitoredItem。订阅创建后服务器主动推送数据变化客户端不用一直问。var subscription new Subscription(session.DefaultSubscription) { PublishingInterval 1000, KeepAliveCount 10, LifetimeCount 30 }; await session.AddSubscription(subscription); subscription.Create(); var item new MonitoredItem { StartNodeId new NodeId(ns2;sChannel1.Device1.Temperature), AttributeId Attributes.Value, SamplingInterval 1000, QueueSize 10, DiscardOldest true }; item.Notification (MonitoredItem monitoredItem, MonitoredItemNotificationEventArgs args) { var notification args.NotificationValue as MonitoredItemNotification; var value notification.Value; // 这里用Invoke回到UI线程更新界面 }; subscription.AddItem(item); subscription.ApplyChanges();PublishingInterval是服务器按这个频率推送数据SamplingInterval是服务器按这个频率去采样底层设备两者可以不同。KepServer这些网关软件本质是把背后PLC的数据通过OPC UA映射出来所以采样间隔一般是100ms到1s之间比较合理太短了会给PLC带来额外通讯负担产线设备偶尔会因此报通讯超时。订阅回调是在后台线程执行的直接操作WinForm的Label会报跨线程异常必须用Invoke或者ProgressT机制转到UI线程。我以前在车间排查一个“界面卡死重启”的问题最后发现是回调里做了数据库写入导致订阅线程被阻塞改成丢到独立Queue里慢慢写就正常了。4. 同时适配西门子机床和KepServer的差异处理4.1 端点发现先把地址和端口逻辑理顺无论是西门子机床的OPC UA服务器还是KepServer它们都遵循同一个规则端点URL基本是opc.tcp://主机名或IP:端口。差别只在默认端口上。KepServer的默认OPC UA端口一般是49320西门子的OPC UA服务器通常是4840。但实际车间环境中客户改端口的情况非常多。所以我的客户端源码里连接地址做成可配置项界面上留输入框配置文件中保存上次连接信息启动时自动填上。有些设备支持多点播报Discovery也就是在网络上广播自己的存在。C#里可以直接用DiscoveryClient.DiscoverServers来扫描局域网内的服务器列表然后把列表显示在界面上点击即可连接。这玩意在现场很好用——你不知道设备的IP和端口时扫一下就能看到服务器名。4.2 安全策略与证书信任两边要求不一样这是我踩坑最多的地方。KepServer默认为了让用户快速上手经常直接用“匿名无加密”的策略也就是SecurityMode为None。但西门子机床的OPC UA服务器往往出于数据安全考虑要求使用Basic256Sha256签名加密并且在第一次连接时校验客户端证书。针对这种差异我在创建Session之前会先从服务器端点的安全策略列表里挑一个最合适的foreach (var ep in selectedEndpoint.EndpointDescription.UserIdentityTokens) { Console.WriteLine(ep.TokenType); } foreach (var secPolicy in selectedEndpoint.EndpointDescription.SecurityPolicies) { Console.WriteLine(secPolicy); }如果服务器同时支持多种策略我优先选择Basic256Sha256因为它安全性最高。但如果测试下来证书互信一直搞不定再退回None。工业现场“先连通、再安全”是务实的选择只要设备在内网隔离的车间环境里风险是可控的。证书互信的流程一定不要忽略首次连接时客户端会从服务器拉取证书并检查是否在受信任列表里。代码中如果发现证书不受信任你可以把它保存到pki/rejected目录并打印提示然后人工把这个证书移动到trusted目录。还有一种更省事的办法在CertificateValidator的CertificateValidation事件里直接接受所有证书但仅限于现场调试用。4.3 命名空间和节点路径的适配西门子机床的OPC UA服务器和KepServer在信息模型上的差异主要体现在命名空间数量和节点层次上。KepServer可以在配置里自定义Channel和Device名称所以NodeId相对固定只要KepServer配置不变节点路径基本稳定。而西门子机床的OPC UA服务器比如Sinumerik系统里集成的UA Server会有很深的层次结构很多数据藏在Objects - Sinumerik - ProgramState - Program这种层级下面不同系统版本甚至节点路径名称都有小差异。我的解决思路是不在代码里写死节点路径而是给每个采集点维护一个“变量表”存JSON或者数据库里。变量表字段包含变量名、NodeId、数据类型、读写属性。客户端启动时加载这张表连接服务器后先做一次节点验证验证失败的变量做个标记并在界面标红但不会导致整个连接中断。这样做的好处显而易见——换了一台不同版本的西门子机床不需要改代码只需要修正变量表里的NodeId即可。5. 实测翻车记录比功能更值钱的排错经验5.1 证书主机名不匹配的经典故障标题里有一句“计算机名不再与OPC UA配置的计算机名称匹配”这是很多人在连西门子设备时遇到过的一个具体报错。报错背后的逻辑是这样的服务器端配置的证书里写了一份“允许连接的计算机名称列表”当客户端发起连接的时候服务器会拿着客户端的机器名去比对发现对不上就拒绝握手。车间里工程师为了方便经常把工控机的计算机名从“WIN7-PC”改成“PLC-HMI-01”但服务器端信任关系还停留在旧名字上于是就这么卡死了。我在代码里解决这个问题的方式是在创建会话时设置checkDomain: false绕过了域名检查。这个参数名看起来不起眼却是连西门子机床时最关键的开关。如果客户的安全策略不允许关闭域名检查就得在西门子服务器的管理界面里把新的计算机名加入白名单这个操作必须在服务器侧完成客户端改不动。5.2 广播找不到服务器但IP能ping通另一个高频现场问题客户端扫描不到OPC UA服务器但明明IP是通的。多半是UDP广播被防火墙拦了或者不在同一网段。OPC UA的Discovery用到了UDP 4840端口而实际连接用的是TCP 4840端口两个方向都要放行。现场处理办法是telnet一下IP端口看通不通telnet 192.168.1.100 4840如果telnet能通说明TCP没问题。如果扫描不到重点检查Windows防火墙有没有放行UDP。还有一种情况是服务器所在工控机装了多个网卡Discovery广播默认从某个非业务网卡发出客户端就收不到了。实在不行就别用广播了手动输入端点URL最靠谱。5.3 读取频率太高服务器不堪重负KepServer背后往往会拖带好几台PLC或者仪表设备OPC UA服务器作为网关本身性能也有限。之前有个项目我在上位机里写了个遍历所有节点的循环每100ms读一次全量数据结果KepServer直接卡死PLC也开始报通讯故障。后来改成按变量变化频率分开订阅高速变化的主轴转速用200ms采样间隔温度压力这类慢变量用1s或者2s读取线程全部停掉服务器负载瞬间就降下来了。还有一个细节订阅的QueueSize如果设置成1表示只保留最新值适合转速这种快速变量。如果要做历史曲线QueueSize可以设大一点比如50这样断网重连后还能补回一小段数据。但注意QueueSize越大内存开销越大全部变量都设50会吃掉不少内存。6. 从“连上”到“用好”这套源码还能扩展什么6.1 报警与事件采集OPC UA除了数据读写还有AlarmCondition模型。KepServer和西门子设备都支持把报警作为事件节点暴露出来。在订阅基础上可以监听事件通知var eventItem new MonitoredItem { StartNodeId new NodeId(ns2;sAlarms), AttributeId Attributes.EventNotifier, MonitoringMode MonitoringMode.Reporting };有了事件订阅上位机就可以实时弹出设备故障报警而不需要轮询检查状态字。这个功能排在扩展项第一位因为车间最关心的永远是设备停了没有为什么会停。6.2 MQTT转发与数据库落盘很多系统要求数据既要进界面又要推到远程平台。OPC UA客户端读到的数据经过格式转换后丢给MQTT客户端发布出去或者用定时批处理写进SQL Server/MySQL。这里我的经验是队列解耦——OPC UA订阅回调只负责把数据压入ConcurrentQueue后台单独线程消费队列写数据库或发MQTT。这样订阅线程永远不会被IO阻塞数据不会丢界面也不会卡。6.3 与视觉、扫码枪等设备联动在做产线自动化的时候C#上位机往往还要接海康相机、扫码枪等设备。OPC UA客户端采集的数据可以作为联动条件比如读到的PLC信号触发相机拍照扫码枪读到条码后写到OPC UA服务器里某个节点让PLC知道当前加工的是哪个工件。这种多设备联动的架构里OPC UA客户端相当于一个“数据中台”把各种数据统一汇入到一个事件驱动框架里整体逻辑会清晰很多。上面这些方向其实都是建立在本文这套“连接、浏览、读写、订阅”源码基础之上的。基础打好上层怎么加功能都不会乱这也正是我做这套源码时最看重的事情——不是堆功能而是把连接层做得足够扎实、足够宽容。以后再遇到什么国产机床、横河仪表、罗克韦尔设备只要对方支持OPC UA用这份代码改改配置就能上省下来的时间大可以花在真正有用的业务逻辑上。本文还有配套的精品资源点击获取