
简介VB .NET OPC通讯示例包面向工业自动化领域的.NET开发者系统演示基于OPC DA/UA协议实现客户端程序的全流程涵盖OPC Foundation库的引用、Group与Item分组模型、本地或远程服务器连接、数据异步读取写入、事件订阅以及多线程并发处理等关键环节。压缩包内共290个文件以vb、frm、resx、cls等VB工程文件为主体辅以h、cpp、cs等跨语言源码和sln、vbproj工程配置同时包含可直接运行的exe、dll整体约2.5MB目录结构清晰便于按模块对照学习。已有607人学习尤其适合需要在.NET Framework 2003至2010各版本之间移植OPC功能的开发者。资源不仅提供从简单数据读写到自定义接口实现的多级示例还展示了Group分组管理、异常与错误处理、多线程异步操作等工程化写法能够帮助开发者深入理解OPC通信原理并快速构建稳定可用的工业数据采集与写入方案。1. VB .Net OPC 通讯例子为什么现成例子总是不够用在工业现场抓数据很多开发者都会先搜“VB .Net OPC 通讯例子”复制下来改一改就指望能连上 PLC。现实往往是例子在开发机上跑得好好的一搬到车间就报错或者连上了但读写不稳定。问题不出在代码逻辑而在协议栈选型、组件位数、DCOM 权限这类例子不会写出来的细节。下面内容不讲空概念直接给出一个能照着改的 DA 例子和一个 UA 例子并把现场最容易翻车的配置和排查顺序一并讲清楚适合那些要在设备数据采集、产线看板、质量追溯项目里把 OPC 通道打通后再去维护后续业务的人。2. 协议与依赖选型DA 还是 UA决定你后面调试方式2.1 OPC DA 与 OPC UA 的本质差异一个靠 DCOM一个靠证书会话OPC DA 和 OPC UA 虽然都挂着一个 OPC 的名字底层却完全是两套实现。OPC DA 把通讯建立在 COM/DCOM 之上要求在客户端和服务器之间完成一套 Windows 平台独有的注册、激活、调用流程。这套机制在单机环境下几乎没有障碍一旦跨机器DCOM 就会化身成一个黑匣子端口随机、身份验证来自注册表、授权失败时只给一句“拒绝访问”。很多老产线还在使用这种结构因为设备端的 OPC 服务器只提供 DA 接口上层系统想采集数据就只能顺着老路走。OPC UA 则把通讯层完全重写了底层换成了 TCP 或 HTTPS 上的协商会话身份校验用应用证书完成。它不需要配置 DCOM 的那一长串权限但多了证书信任和端口管理两个新任务。比如大多数 OPC UA 设备默认监听 4840 端口防火墙放行后才能握手成功。在 VB .NET 里接入 DA 的常规做法是添加 COM 引用创建 OPC 自动化对象接入 UA 则是引用托管客户端 SDK代码更像普通类库调试的时候也更好观察内部状态。选型我一般不会等到写代码时再纠结而是对着现场设备资料先确认一次对方给的是 DA 接口还是 UA 接口。新建项目而且设备支持 UA 时优先 UA老产线只有 DA 服务器或者上位机系统强制要求 DA就老老实实 DA。两种协议混接在一个程序里不是不行但连接、鉴权、断线重连的逻辑完全不同混用会把你排查故障的时间拉长一倍。从工程成本上看UA 虽然增加了证书管理但它不需要 DCOM 动态端口后期多台客户端部署时比 DA 省事得多。经验告诉我当客户追问“为什么点位读出来是空值”时我基本能根据协议类型提前判断出问题出在哪一侧这种判断力来自对协议栈的理解不是某个调试技巧。2.2 版本、位数与依赖清单动手写代码前先确认这三件事最容易踩的一个坑是 OPC DA 的 COM 组件在 64 位系统里没有被注册到正确的位置。VB .NET 项目默认平台目标是 AnyCPU跑在 64 位系统里就是 64 位进程可不少老 OPC 服务器的自动化组件只提供了 32 位版本注册表里的 CLSID 只存在于 Wow6432Node 分支下64 位进程按 ProgID 去创建对象时直接报“类未注册”。我只要确认要走 DA第一件事就是把项目平台目标改成 x86这个习惯帮我省掉了大量玄学问题。现在的工控机基本都是 64 位 Windows 也能运行 32 位程序所以这样改几乎没有副作用。第二个要确认的是服务器到底部署在哪里。DA 服务器和客户端在同一台机器上时Connect 里的主机名可以传 Nothing跨机器时就必须确认 DCOM 权限并且固定一个服务账号来运行 OPC 服务器。很多工程师只把电脑加入同一个工作组就以为万事大吉结果在服务器端一台机器装完组件后还是连不上最后发现是防火墙没给动态端口留空间。我的做法是在组件服务里把 DCOM 的动态端口范围固定下来避免每次通讯都随机协商一个新端口抓包时能看到一个相对稳定的连接模式现场排障会舒服很多。第三个是依赖组件。DA 客户端机器必须安装并注册 OPC Core Components否则连服务器列表都会是空的。UA 客户端则需要对应 SDK 的运行时以及正确存放证书的位置。这些东西在例子里不会写代码只负责调用环境不到位代码再对也跑不起来。所以每次接到任务我不急着打开开发环境建工程而是先到现场把这三件事确认完再开始写代码。2.3 用一张检查表确定协议选型我把进场排查的顺序整理成一张表每次到新现场都按这个走一遍检查项DA 重点关注UA 重点关注通讯端口不固定受 DCOM 动态端口影响默认 4840需防火墙放行身份校验DCOM 用户权限应用证书 用户令牌位数一致性客户端与 COM 组件位数必须一致托管代码无需过分关心依赖安装OPC Core ComponentsSDK 运行时库断线后的行为连接对象原地失效需重建会话可重连但订阅需重建这张表列的就是我选型时的底线要求。DA 行里最难的是“位数一致”和“断线后的行为”UA 行里最难的是“证书信任”。后面两章分别给出最小例子你再回头看这张表每个坑都能对上号。3. 最小可跑的 VB .Net OPC DA 通讯例子3.1 添加 COM 引用与建立连接的核心代码OPC DA 的自动化接口在 Windows 里是一个 COM 组件。VB .NET 项目里添加 COM 引用后就能使用 OPCServer、OPCGroup、OPCItem 这些互操作类。我一般会在项目里加一个独立的客户端类把连接、断开、读、写都封装起来业务层不直接碰 COM 对象。下面代码先建立一个可以复用的连接类Imports OPCAutomation Imports System.Runtime.InteropServices Public NotInheritable Class OpcDaClient Private server As OPCServer Public Function Connect(serverProgId As String, hostName As String) As Boolean Try 创建 COM 服务器对象实际就是实例化一个 OPC 服务器的自动化包装 server New OPCServer() 参数一 serverProgId: OPC 服务器的注册标识不要自己编 参数二 hostName: 机器名本机传 Nothing跨机器传目标机器名 参数三保留参数固定传 Nothing server.Connect(serverProgId, hostName, Nothing) Return server IsNot Nothing Catch ex As Exception Console.WriteLine(连接失败: ex.Message) Return False End Try End Function Public Sub Disconnect() If server Is Nothing Then Return Try 先清掉所有组再断开顺序反了会让组对象残留 If server.OPCGroups.Count 0 Then server.OPCGroups.RemoveAll() server.Disconnect() Marshal.FinalReleaseComObject(server) Catch Finally server Nothing End Try End Sub End Class这段代码的关键在第一处 Try 块。ProgID 不是代码里写出来的字符串而是 OPC 服务器安装时写入注册表的注册标识。现场最好用服务器自带的配置工具查看或者用 OPC 枚举工具扫描一遍确认了 ProgID 再填进 Connect。如果你只照着网上的例子把某个名字写死很容易在 Connect 这一步就抛“服务器未找到”。Connect 的第三个参数在自动化接口里是一个保留参数大多数实现要求传 Nothing传了字符串反而可能多一段不必要的解析。断开连接的部分容易被忽略。许多人写程序只调用 Disconnect 就结束但 COM 对象的引用计数不会立刻归零进程关不掉后台会残留一个不可见的进程。我在 Disconnect 里先 RemoveAll 清掉所有组再 Disconnect最后用 FinalReleaseComObject 强制释放这样可以保证下次启动时不会出现端口占用或者服务器进程无法退出的问题。3.2 按 Tag 读取点位组、项与同步读的完整写法OPC DA 的读取并不是直接给服务器一个字符串就返回值。你需要先创建一个“组”对象往组里加入“项”项指向 OPC 服务器的具体数据点然后再用 SyncRead 方法读取。为什么要这层间接关系因为服务器为了性能把一组点位聚到同一个组里统一做缓存和死区过滤这样比一个个独立读要高效得多。下面是一个读取单个点位的代码Public Function ReadTag(groupName As String, itemId As String, clientHandle As Integer) As Object If server Is Nothing Then Throw New InvalidOperationException(请先调用 Connect) Dim group As OPCGroup Nothing Dim item As OPCItem Nothing Try 添加一个激活的组更新周期 1000 毫秒客户句柄为 1 group server.OPCGroups.Add(groupName, True, 1000, clientHandle, 0) 第二个参数是客户端句柄写 2方便回调时识别 item group.OPCItems.AddItem(itemId, 2) 构造出数组参数OPC Automation 接口要求传数组 Dim itemCount As Integer 1 Dim itemHandles As Array Array.CreateInstance(GetType(Integer), itemCount) itemHandles.SetValue(item.ServerHandle, 0) Dim values As Array Nothing Dim errors As Array Nothing Dim qualities As Array Nothing Dim timestamps As Array Nothing 第一个参数 1 表示从设备读取第二个参数 1 表示读 1 个项 group.SyncRead(1, itemCount, itemHandles, values, errors, qualities, timestamps) If errors.GetValue(0) 0 AndAlso values.GetValue(0) IsNot Nothing Then Return values.GetValue(0) Else Return Nothing End If Finally 这个示例里每次临时建组用完即删正式项目建议缓存句柄 If group IsNot Nothing Then server.OPCGroups.Remove(group) End Try End Function这段代码里最重要的概念是“组”和“项”。ReadTag 返回的是 Object因为 OPC 服务器读回来的值可能是布尔、数值、字符串或时间VB .NET 里直接拿 Object 接在业务层再做类型转换。SyncRead 的参数顺序非常容易写错尤其是第一位的 Source 参数传 1 是让服务器从设备数据源读取传 0 是从服务器缓存读取。如果你需要实时数据用 1如果只是看历史趋势用 0 会更快但可能拿不到最新值。后面四个数组分别代表服务器句柄、值、错误码、质量、时间戳。判别是否成功的依据是 errors 数组而不是 values 是否为空。有时候 values 里会有值但 errors 返回的却是非零错误码那是设备端的数据质量不可靠不能当作正常数据用。质量数组在 DA 里通常 192 表示 good但为了安全我仍建议以 error 为准质量数组只用于辅助判断。3.3 写入点位同步写的参数陷阱与缓存思路写入在 OPC DA 里同样需要一个组和一个项但调用的方法是 SyncWrite。很多新手把写入想成本地赋值直接调用一个 WriteValue 方法结果发现没有任何效果原因就是没有先建立“项”这个中间对象。下面是一个写入的最小实现Public Function WriteTag(groupName As String, itemId As String, value As Object, clientHandle As Integer) As Boolean Dim group As OPCGroup Nothing Try group server.OPCGroups.Add(groupName, True, 1000, clientHandle, 0) Dim item As OPCItem group.OPCItems.AddItem(itemId, 3) Dim itemCount As Integer 1 Dim srvHandles As Array Array.CreateInstance(GetType(Integer), itemCount) srvHandles.SetValue(item.ServerHandle, 0) Dim values As Array Array.CreateInstance(GetType(Object), itemCount) values.SetValue(value, 0) Dim errors As Array Array.CreateInstance(GetType(Integer), itemCount) NumItems 传 1后面三个数组按位置对齐 group.SyncWrite(itemCount, srvHandles, values, errors) Return Convert.ToInt32(errors.GetValue(0)) 0 Finally If group Is Not Nothing Then server.OPCGroups.Remove(group) End Try End Function写操作有三个容易翻车的地方。一是值的类型必须和设备数据类型一致设备端定义的是 INT 型你传一个字符串进去服务器会直接返回类型不匹配错误。二是写操作对某些设备点是危险的比如写转速、写温度设定值建议在工控程序里加一道确认按钮不要用定时器自动写。三是错误码必须记录OPC 写失败的原因很多值域超过上限、设备离线、服务器写保护都会返回错误直接 Return False 后要把错误码写入日志否则后面无从定位。这个 DA 例子虽然是“最小”但已经能把组、项、句柄这些核心概念带出来。它适合跑通链路不适合直接上生产。生产的做法是把组和项的句柄缓存起来不要每次读都 AddItem这样调用频率高了以后才不会越来越卡。我在最后一章会专门讲缓存和重连的思路。4. 最小可跑的 VB .Net OPC UA 通讯例子4.1 连接 UA 服务器端点发现、证书处理与会话保持OPC UA 客户端在 .NET 生态下有多种实现但代码脉络基本统一加载应用配置发现服务器端点选择一条安全策略创建会话。和 DA 相比UA 不需要处理 DCOM但多出一套“证书”的管理步骤。下面按主流 UA 客户端 SDK 的调用方式来写类名和命名空间在现成 SDK 里几乎一致。不同的 SDK 在构造函数的参数上可能有细微差别粘贴后先看智能感知对齐参数名就行了。Imports Opc.Ua Imports Opc.Ua.Client Public Class OpcUaClientExample Private _session As Session Public Function Connect(endpointUrl As String) As Boolean 应用配置UA 客户端需要一个 ApplicationName 和 ApplicationUri Dim config As New ApplicationConfiguration With { .ApplicationName VB.NET OPC UA Demo, .ApplicationUri urn:demo:vbnet:opcua, .SecurityConfiguration New SecurityConfiguration With { .AutoAcceptUntrustedCertificates True } } 发现端点服务器可能暴露多个安全策略这里取第一个“支持匿名 无安全”的端点 Dim discovery New DiscoveryClient(endpointUrl) Dim endpoints As EndpointDescription() discovery.GetEndpoints() discovery.Close() 在实际项目里建议根据服务器要求选择 Basic256Sha256 策略 Dim endpoint As EndpointDescription endpoints(0) 创建会话。最后一个参数是会话超时毫秒数比如 60000 _session Session.Create(config, endpoint, vbnet-client, 60000, New UserIdentity(Nothing), Nothing) Return _session IsNot Nothing AndAlso _session.Connected End Function Public Sub KeepAlive() 每 5 秒发送一次心跳避免中间的网关或设备把空闲会话断开 _session.KeepAliveInterval 5000 End Sub End Class这段代码里最应该警惕的是 AutoAcceptUntrustedCertificates。开发环境打开它很省事但到了生产环境一定要关掉改成把服务器证书导入到 Windows 受信任的根证书存储里。否则你为了图方便默认接受所有证书等于把 OPC UA 的安全机制直接拆掉在合规审计时很难交代。Session.Create 的第五个参数是 UserIdentity这里传 Nothing 代表匿名大多数设备服务器默认开启匿名通道如果设备设置了用户名密码则需要传 New UserIdentity(用户名, 密码)。第六个参数是语言列表按客户端系统语言自动匹配没有特殊需求可以不传。端点发现这里还有一个常见问题有的服务器只监听本机地址不回地上报端点。如果你在工控机上测试时用 127.0.0.1 能连换到局域网 IP 就找不到端点要么服务器改监听地址要么检查防火墙是不是挡掉了 4840 端口的入站规则。现场最常见的现象是 Windows 防火墙弹窗没有点允许后面节点列表一片空白。4.2 读节点、写节点与订阅数据变化UA 里节点的标识通常写成“ns2;s标识名”这样的字符串。ns 是命名空间索引s 是字符串类型的标识。读取一个节点只需构造 NodeId 再调用 ReadValue写一个节点则把目标属性打包成 WriteValue。下面把读写和订阅都串起来Public Function ReadNode(nodeIdString As String) As Object NodeId 可以由字符串直接解析格式ns2;sxxx Dim nodeId As New NodeId(nodeIdString) Dim value As DataValue _session.ReadValue(nodeId) Return value.Value End Function Public Function WriteNode(nodeIdString As String, newValue As Object) As Boolean Dim nodeId As New NodeId(nodeIdString) Dim valueToWrite As New Variant(newValue) Dim writeValue As New WriteValue With { .NodeId nodeId, .AttributeId Attributes.Value, .Value New DataValue(valueToWrite) } Write 方法接受一个 WriteValue 数组可以一次写多个点 Return _session.Write(New WriteValue() {writeValue}) StatusCodes.Good End Function Public Sub SubscribeNode(nodeIdString As String, groupName As String) Dim subscription As New Subscription(_session) With { .PublishingInterval 500, .KeepAliveCount 10 } Dim nodeId As New NodeId(nodeIdString) Dim monitor As New MonitoredItem With { .StartNodeId nodeId, .AttributeId Attributes.Value, .SamplingInterval 250, .QueueSize 5 } 数据变化时触发回调VB.NET 里可以用 AddressOf 绑定到方法 AddHandler monitor.DataChange, AddressOf OnDataChange subscription.AddItem(monitor) _session.AddSubscription(subscription) subscription.Create() End Sub Private Sub OnDataChange(monitor As MonitoredItem, e As MonitoredItemNotificationEventArgs) Dim value As DataValue DirectCast(e.NotificationValue, DataValue) Console.WriteLine(节点变化: monitor.StartNodeId.ToString() 值: value.Value) End Sub读写参数要区分清楚ReadValue 是同步读适合单点Write 方法是批量写它接收的参数是 WriteValue 数组所以哪怕只写一个点也要构造数组。订阅这部分PublishingInterval 是订阅的发布间隔也就是服务器检查数据变化并向客户端推送的周期500 毫秒是个偏保守但 CPU 占用不高的值SamplingInterval 是采集周期250 毫秒代表设备侧采样频率两者不是一个概念。很多代码里把这两个值搞混导致订阅的数据变化频率和预期完全不同。QueueSize 表示服务器端在客户端没有及时取数据时最多缓存几条数值太小会丢数据数值太大会增加内存占用。订阅的优点是服务器只在数据变化时推送比线程里 while 循环轮询省资源。但它也有代价如果现场网络闪断订阅会在会话重建后丢失你必须在重连逻辑里重新创建订阅。这个我在避坑章节里会单独说。实际项目里不要每变化一次就创建一个订阅最好是把需要监听的节点集中放在同一个订阅里让服务器一次性管理。5. OPC 通讯避坑与排查高频问题记录5.1 连接 DA 服务器报“拒绝访问”DCOM 权限背锅现象是 VB .NET 程序在开发机上连本机 OPC 服务器完全没有问题部署到另一台电脑后代码在 server.Connect 这一步抛异常错误信息是“拒绝访问”甚至“服务器运行失败”。很多人的第一反应是代码写错其实这是 DCOM 权限在拦你。原因在于 OPC 服务器默认只允许特定用户调用 COM 接口新电脑的登录账号没有授权。解决方法是把部署机器的运行账号加入 OPC 服务器的 DCOM 权限列表。具体操作是先运行 dcomcnfg打开组件服务再打开 OPC 服务器的属性把“标识”设为交互式用户或指定一个固定账号同时在“启动权限”和“访问权限”里给该账号加进允许列表。做完这两步基本就能连上。生产环境建议用独立服务账号不要用管理员账号裸奔。5.2 OPC 服务器列表一片空白枚举服务没生效现象是代码调用服务器枚举器返回数为 0但 OPC 服务器明明已经安装在同一台机器上。第一次遇到这个问题的开发者很容易怀疑服务器没装好重装一遍还是一样。原因通常有两个一是 OPC Core Components 里的枚举组件没有注册或者被安全软件清理掉二是当前开发机没有读取注册表枚举项的权限。解决方法是重新安装对应版本的 OPC Core Components然后用管理员身份注册如果仍为空就检查组件服务里的枚举服务是否处于停止状态。DA 的枚举只对注册表里的所有 ProgID 扫一遍服务器只要注册了就不该看不到。做这一步之前先确认注册表里能找到服务器 CLSID能避免你怀疑人生。5.3 32 位和 64 位不匹配COM 类型转换直接抛异常现象是程序编译时一切正常运行到读取点位时返回的 values 数组里全是空值或者直接在对象转换时抛 InvalidCastException。这个问题在 64 位工控机上特别常见。原因是 COM 组件存在两套注册表分支。你的项目如果还是 AnyCPU在 64 位系统下会自动以 64 位进程运行但 OPC 服务器的自动化组件却注册在 32 位注册表里两边就对不上了。解决方法是把 VB .NET 项目的目标平台强制改为 x86重新编译后再看。如果现场服务器本身就是 64 位进程提供的 COM 服务那反过来把项目设成 x64。判断依据是看 OPC 服务器安装目录里有没有 SysWOW64 字样或者直接用注册表编辑器查看 CLSID 在哪个分支下存在。5.4 UA 证书校验失败报 BadSecurityChecksFailed现象是 OPC UA 客户端连接时服务器返回 BadSecurityChecksFailed或者客户端回调里显示 Application Certificate Untrusted。开发环境下很多人会把它当成偶发网络故障反复重试浪费时间。原因其实是客户端首次连接时的证书没有导入到服务器信任列表。OPC UA 的双向认证要求服务器信任客户端应用证书反过来客户端也要信任服务器证书。解决方法是第一次连接前把客户端生成的证书导出给服务器管理员在服务器端添加为受信任客户端同时把服务器证书抓下来导入到客户端机器受信任人列表。如果你只是在开发环境验证逻辑可以临时把 AutoAcceptUntrustedCertificates 设为 True但上线前一定换回正常信任流程否则后患无穷。5.5 会话重连后订阅丢失数据不再上报现象是现场断网一段时间后网络上其他业务恢复了OPC UA 程序也显示已连接但订阅的数据事件彻底不来了只剩手动读节点还能返回数值。原因是 OPC UA 的订阅是建立在一条 Session 上的网络中断后 Session 被服务器回收SDK 虽然能自动恢复连接但订阅对象不会跟着自动重建。解决方法是把订阅创建逻辑封装成一个方法在重连成功回调里先清空原订阅再根据本地保存的节点列表逐条重建。很多人没有保存订阅节点清单重连时不知道恢复哪些点就会留下这个坑。建议维护一个 List(Of String) 的订阅节点清单每次订阅成功时刷新一次重连时直接从这个清单重建。这几条基本覆盖了现场遇过的七成以上问题。遇到连不上、读不到、写不进先按现象对照这一章找原因不要急着改业务代码。6. 让 OPC 通讯代码能一直稳定跑重连、日志与性能技巧最后落到生产环境。OPC 通讯在项目里往往不是独立的后面还挂着实时看板、批次报表、报警联动通讯一旦不稳定脏数据比没数据还讨厌。我现在给所有 OPC 客户端做三件事断线自动重连、关键日志落盘、禁止高频临时建项。先说重连。DA 和 UA 的断线判定不一样。DA 的 OPCServer 对象在断网后往往直接失效必须重新 New 一个新对象UA 的 Session 可以先调用一次 GetEndpoints 探测连接再决定是否重建。通用做法是开一个定时器间隔 5 秒检查一次连接状态断开时先等待一个退避周期再重连。Private Sub Timer_Tick(sender As Object, e As EventArgs) Handles Timer.Tick If _client.IsConnected Then _failCount 0 Return End If 连续失败超过 3 次把重试间隔拉长到 30 秒避免服务器压力过大 _failCount 1 Dim delaySeconds As Integer 5 If _failCount 3 Then delaySeconds 30 If (DateTime.Now - _lastTry).TotalSeconds delaySeconds Then _lastTry DateTime.Now _client.Connect(_serverUrl) If _client.IsConnected Then _failCount 0 RebuildSubscriptionIfAny() End If End If End Sub其中 _failCount 是模块级变量记录连续失败次数。第一次失败 5 秒后重试连续失败 3 次以上就把间隔拉长到 30 秒这是非常简单但可靠的退避方式。不要用 Thread.Sleep 写在 while 循环里界面会卡死建议用定时器。重连成功后还要记得重建 UA 订阅否则会出现上一章第 5 种问题。再说日志。OPC 通讯的故障往往是偶发的最难查的就是“某个时间点值不对”。我给每个读写方法外面包了一层日志把调用时间、节点 ID、返回值、错误码写入一个本地文件按天滚动单条内容控制在 200 字节以内。这层日志不需要额外装框架直接在文本框里 AppendLine 后落文件就行关键是别把值的内容打印成多行 JSON否则现场排错时根本对不齐。最后是性能。DA 那边即使封装了 ReadTag也不要每次循环读值都 AddItem 再 Remove。组的本质是给服务器做批量缓存和订阅如果你连续读同一个点都临时建一次项服务器端就会反复做插入和释放CPU 和内存一起飙升。我用 ConcurrentDictionary 把组和项的句柄缓存起来键是 groupName itemId只在第一次访问时 AddItem后续直接复用。UA 那边则优先用批量读一次 ReadNode 只能读一个点但 session.Read 可以一次读一组 NodeId吞吐量差出一个量级。还有一个习惯可以分享所有 OPC 通讯程序的重点不是界面是否好看而是程序退出时能否优雅断开。我早期做某图像处理 Demo 时直接关窗体导致 COM 线程不能退出、进程僵在后台后来定下规矩FormClosing 事件里一定要先走 Disconnect 方法再释放 COM 对象最后再真正退出。这个习惯从那时保持到现在救了我好几次。如果你也是从“搜索引擎找到 VB .Net OPC 通讯例子然后开始改造”这条路走过来的希望这篇能帮你把重心放在代码之外的那部分上。协议选型、环境确认、重连和日志这些才是现场稳定的关键。希望帮到你。本文还有配套的精品资源点击获取