ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Java连接传统OPC DAServer全攻略:JeasyOpc与JoPC实测对比

Java连接传统OPC DAServer全攻略:JeasyOpc与JoPC实测对比 简介OPC是工业自动化数据交换的通用接口Java原生并不直接支持需借助第三方库桥接COM。这份资源面向需要在Java环境中对接OPC Server的开发者围绕JOPC与JeasyOPC两套库给出从初始化连接、浏览组与项、订阅数据变化、读写数值到断开连接的完整实现链路并配套了针对无内存泄漏及双客户端场景的专项示例。压缩包共860个文件约1.99MB除大量Java源码与class编译文件外还包含Delphi工程.pas/.dcu、配置文件.cfg/.xml/.properties及调用所需的jar与dll可辅助理解跨语言封装和OPC客户端的底层细节。已有1634人学习下载适合具备一定Java基础、正在从事工业数据采集或系统集成的读者。通过研读与运行示例能快速掌握JOPC和JeasyOPC的核心API用法同时减少处理COM交互、类型转换与资源释放时的踩坑时间。 先说结论Java要连传统OPC DA Server社区里玩得通的基本就两条路一条是JeasyOpc一条是JoPC也就是org.openscada.opc.lib。两个库我都拿到产线上实测过下面把从环境准备、DCOM权限配置到具体连点读数的完整流程写清楚。这篇文章适合做MES、SCADA、设备数据平台的人看尤其是第一次用Java对接Kepware、SimaticNET这类OPC Server的兄弟可以直接照抄。先交代一个前提OPC UA和OPC DA是两个世界UA基于TCP/IPDA基于Windows COM/DCOM。JOpc和JeasyOpc处理的都是传统OPC DA走的是DCOM通道。如果你现场已经是OPC UA Server那方案完全不同别搞混。下面的内容全部围绕“Java进程作为OPC DA客户端”来展开。1. 先把两个库的关系理清楚为什么都绕不开J-Interop1.1 J-Interop是所有事情的底层第一个要搞明白的问题Java为什么不能直接连OPC DA因为OPC DA的通信协议建立在Windows COM/DCOM之上而Java标准库根本没有调用COM组件的通道。C#可以用OPC Foundation提供的SDKVB可以用现成的ActiveX控件Java只能靠桥接。桥接方案早年很痛苦常见做法是用JNI调本机的opcdaauto.dll也就是OPC官方自动化接口的包装。但这么搞要先装OPC Core Components还要处理一堆本机依赖环境稍微一变就崩。后来社区出现了J-Interop它直接在Java层实现了DCOM的线上协议Java进程可以通过网络调用远程Windows机器上的COM对象。这套东西最核心的价值是JVM所在机器不需要注册任何OPC相关的COM组件也不需要装OPC SDK。你在Linux服务器上跑Java进程照样可以去连接Windows工控机上的OPC Server这是很多项目能落地的根本原因。JOpc和JeasyOpc都是在J-Interop之上封出来的客户端库。它们的底层是同一个所以遇到连不上的问题排查思路也基本一致。1.2 JeasyOpc与JoPC的区别与选型建议JeasyOpc的包名是javafish.clients.opcAPI设计极其精简一个JOpc类几乎干了所有事情连接、建组、加项、同步读、同步写、异步回调。好处是上手快几十行代码就能跑通坏处是文档少而且项目已经很长时间没更新很多老版本代码在网上一搜一大把但不同版本的构造参数、方法签名差异很大复制过来经常编译不过。JoPC在工程圈通常指OpenSCADA维护的org.openscada.opc.lib同样基于J-Interop但API组织得更清晰分成了ConnectionInformation、Server、Group、Item、ItemState这些类还提供了DataCallback回调接口和连接状态监听。在Maven仓库里能直接拉依赖维护状态比JeasyOpc好不少。选型上我的经验是临时验证、点位不多、追求最快跑通用JeasyOpc正式项目、点位多、要长期维护用JoPC。两个库我都用过后面分别给代码你按自己的场景挑一个就行。对比项JeasyOpcJoPC (openscada.opc.lib)Java包名javafish.clients.opcorg.openscada.opc.lib.daAPI风格单类大而全极简面向对象职责清晰回调支持支持但配置较隐晦支持DataCallback接口明确Maven坐标基本靠jar包引入仓库可搜坐标稳定线程安全一般多线程需自行加锁官方文档说明线程安全维护状态停滞多年社区仍在维护适合场景快速验证、小点位正式采集项目、大点位2. 动手之前的环境准备DCOM、防火墙和CLSID2.1 OPC Server的ProgID与CLSID获取连OPC Server之前先把Server的信息摸清楚。你需要两个东西机器的IP或主机名以及OPC Server的ProgID或CLSID。ProgID是给人类看的名字比如Kepware的Kepware.OPCClient、西门子的OPC.SimaticNET。CLSID是Windows注册表里的GUID形如{F8582CF2-88FB-11D0-B850-00C0F0104302}。很多库构造函数里支持直接传ProgID由库内部解析成CLSID但JoPC的ConnectionInformation通常直接填CLSID。CLSID的获取方法很土但很有效在OPC Server所在机器上打开注册表定位到HKEY_CLASSES_ROOT\你的ProgID\CLSID右侧那个字符串值就是。比如HKEY_CLASSES_ROOT\Kepware.OPCClient\CLSID如果注册表里找不到也可以在OPC Server的安装目录或配置界面里找实在不行用OpcEnum工具枚举网络上可用的OPC Server它会直接显示ProgID和CLSID。这里提前把CLSID记下来后面调试能省大事。2.2 DCOM权限配置要点很多人代码写得没问题但一跑就报0x80070005访问拒绝九成是DCOM权限没配好。在OPC Server所在的Windows机器上执行dcomcnfg打开组件服务依次进入“组件服务 - 计算机 - 我的电脑 - DCOM配置”找到你的OPC Server对应的项右键打开属性。重点看两个地方“标识”选项卡选择“交互式用户”。如果你用指定账户必须保证密码正确且该用户有“作为批处理作业登录”权限否则即使配置对了也会报登录失败。“安全”选项卡“启动和激活权限”和“访问权限”里至少要把你Java进程所在的Windows账户加进去并授予允许权限。如果客户端机器和工作组环境下不好管理可以临时把Everyone加进去验证连通性确认通了之后再收紧。还有一个很容易忽略的坑dcomcnfg里除了单个组件的配置还有“我的电脑”右键属性里的“默认属性”和“默认安全设置”。某些Windows版本会受默认限制影响如果单个组件配置了仍然报权限错误去“我的电脑”的默认安全设置里也把用户加上。2.3 防火墙与网络验证DCOM不止用135端口后续数据通道还会动态分配端口。最省事的做法是把OPC Server的进程加到防火墙例外里或者干脆在生产环境把OPC Server机器上的相关端口段放通。防火墙这块我建议流程是这样的先用OPC Server自带的客户端工具或者第三方的OPC Client比如Matlab、Kepware自带的Client在客户端机器上连接一次确认Server本身没毛病。这一步通过之后再进Java代码调试。如果这一步都连不上问题在DCOM/防火墙/网络层面不要再怀疑代码。这个习惯能帮你省掉后面90%的排查时间。3. JeasyOpc实战连接、建组、读点、写值、回调3.1 依赖与最小连接代码JeasyOpc现在基本靠直接引入jar包或者从GitHub镜像找release。我用的版本是比较常见的1.4/1.5分支包名都是javafish.clients.opc。下面这段是完整的最小连接代码import javafish.clients.opc.JOpc; import javafish.clients.opc.component.OpcGroup; import javafish.clients.opc.component.OpcItem; import javafish.clients.opc.variant.Variant; public class JeasyOpcDemo { public static void main(String[] args) throws Exception { // 初始化COM线程模型每个线程只需要初始化一次 JOpc.coInitialize(); JOpc.setTrace(true); // 主机IP、OPC Server的ProgID、客户端名称 JOpc opc new JOpc(192.168.1.10, Kepware.OPCClient, JavaDemo); // 创建组5个参数视版本而定组名、更新周期(ms)、死区 OpcGroup group new OpcGroup(group1, 1000, 0.0f); // 创建项项名和是否激活 OpcItem item new OpcItem(Channel1.Device1.Tag1, true); group.addItem(item); // 添加组并连接 opc.addGroup(group); opc.connect(); if (opc.ping()) { System.out.println(OPC Server连接成功); } // 同步读取 Variant value opc.syncRead(group, item); System.out.println(读取结果: value); opc.disconnect(); } }这段代码有几个容易踩的细节。coInitialize()是COM线程模型初始化在Windows环境下必须调用否则后续COM调用会直接异常。JOpc.setTrace(true)会打开内部通信日志调试阶段建议一直开着能看到连接过程中哪一步失败。OpcGroup的构造参数不同版本还不一样有的版本第三个参数是死区deadband有的版本可能还多一个keepAlive参数。建议写代码前用IDE看一下jar包里的构造函数签名别盲目抄网上的。死区的含义是数值变化百分比小于这个值时不触发回调单位是百分比比如0.5表示0.5%。生产环境一般设1%~2%设0会非常频繁地推送。3.2 同步读取与类型解析syncRead返回的Variant是JeasyOpc对COM VARIANT的封装里面带类型信息。常见的类型有VT_I2short、VT_I4int、VT_R4float、VT_R8double、VT_BSTRString、VT_BOOLboolean。实际项目里建议根据点位类型做一次显式转换Variant value opc.syncRead(group, item); int type value.getType(); switch (type) { case Variant.VT_R4: float f value.getFloat(); break; case Variant.VT_R8: double d value.getDouble(); break; case Variant.VT_BSTR: String s value.toString(); break; default: System.out.println(value.toString()); }不要直接拿到Variant就往数据库里塞不同Server返回的类型可能不一样比如同一个温度点位Kepware可能返回VT_R4SimaticNET可能返回VT_R8。不统一处理的话后面报表和报警逻辑会很痛苦。3.3 数据变化回调与异步更新同步读适合点位少、轮询频率低的场景。如果点位多、要求毫秒级刷新就得上回调。JeasyOpc的异步监听是通过全局监听器实现的JOpc.addOpcGroupListener(new OpcGroupListener() { Override public void dataChanged(OpcGroup group, OpcItem[] items) { for (OpcItem item : items) { Variant v item.getValue(); System.out.println(item.getItemName() v); } } });这里有一个大坑回调只有在异步组模式下才触发。JeasyOpc建组的时候带JOpc.ASYNC_GROUP标记的才是异步组否则dataChanged永远不会被调用。老版本代码里很常见的是JOpc opc new JOpc(host, progID, client); opc.addGroup(group);然后又想用回调结果数据不来就是这个原因。另外要注意线程安全。JeasyOpc的连接对象在多线程环境下不是天然安全的回调线程和业务线程可能会同时操作同一个连接。我的做法是把所有对opc对象的读写操作都收敛到一个业务线程里或者给共享的连接对象加锁。多线程并发读写同一个连接偶发崩溃很难查。3.4 JeasyOpc版本的坑网上搜JeasyOpc的代码经常能看到互相矛盾的写法因为老项目分叉版本太多了。有的代码用OpcItem item new OpcItem(...);不带第二个参数有的用new OpcItem(..., true)带激活标志。有的JOpc构造是三参有的是两参。这都不是你写错了而是jar包版本不同。我的建议是选定一个版本后以你本地jar包的实际API为准不要被网上的老代码带偏。如果项目允许还是优先用JoPCAPI稳定性和可维护性好很多。4. JoPC(OpenSCADA)实现同一套流程更适合生产项目的写法4.1 Maven依赖与连接配置JoPC的Maven坐标在不同时期有变化老的Atlantis版本和后续的独立项目坐标不一样。你直接搜org.openscada.opc.lib选择和你JDK版本兼容的最新release即可。坐标这一块因仓库源不同会略有差别以你本地仓库能拉到的为准。连接配置比JeasyOpc更结构化import org.openscada.opc.lib.common.ConnectionInformation; import org.openscada.opc.lib.da.*; public class JoPcDemo { public static void main(String[] args) throws Exception { ConnectionInformation ci new ConnectionInformation(); ci.setHost(192.168.1.10); ci.setDomain(); ci.setUser(); ci.setPassword(); // 这个CLSID是Kepware的常见值换成你自己的 ci.setClsid(F8582CF2-88FB-11D0-B850-00C0F0104302); Server server new Server(ci, null); server.connect(); System.out.println(连接成功); Group group server.addGroup(g1); Item item group.addItem(Channel1.Device1.Tag1); // 同步读取 ItemState state item.read(true); System.out.println(state.getValue()); // 写入 item.write(123.0); server.dispose(); } }ConnectionInformation里设置用户、密码的地方对应DCOM连接时的Windows凭据。大部分内网环境不启用域认证domain和user留空字符串也可以但在某些Kerberos域环境下必须填真实账户否则会报访问拒绝。这个跟JeasyOpc是同一个问题只是CLSID比ProgID更底层更容易排查。4.2 建组、读点、写值JoPC的Group和Item封装得比JeasyOpc更顺手。group.addItem(点位名)返回的就是Item对象读写都挂在Item上语义清晰。Item item group.addItem(Channel1.Device1.Tag1); ItemState state item.read(true); System.out.println(值: state.getValue()); item.write(99.5);read(true)第二个参数表示是否强制从Server端读取最新值如果传false可能返回本地缓存。生产环境如果需要拿实时值建议都传true。写入的时候要注意类型匹配。Server点位如果是整型你传个double进去部分OPC Server会拒绝。稳妥起见先读一次拿到值的类型再按相同类型写。4.3 回调模式JoPC的回调比JeasyOpc直观很多group.addItem(Channel1.Device1.Tag2, new DataCallback() { Override public void changed(Item item, ItemState state) { System.out.println(item.getID() state.getValue()); } });带回调的addItem会订阅这个布尔量的变化Server端一旦有数据变化changed方法就会被调用。这里有两个使用要点第一回调线程来自J-Interop内部不要在changed里做阻塞操作比如发HTTP请求、写数据库。正确做法是把数据扔进一个内存队列由单独的业务线程消费这样即使下游处理慢也不会阻塞OPC通道。第二JoPC是线程安全的多个线程可以共享同一个Server对象做读写。但回调频率高时注意消费速率否则内存队列会积压。实践中我常用BlockingQueue容量设个上限满了就丢弃最老的或记录下来防止内存撑爆。4.4 与JeasyOpc的工程对比我在同一个项目里分别用两个库写过采集模块一点真实感受JoPC的异常体系更完整。连接失败、DCOM错误、回调注册失败抛出的异常类型都能对应到具体原因StackOverflow上都搜得到。JeasyOpc很多时候异常信息就是一句话OPC error全靠猜。JeasyOpc的优势是代码量少。如果只是临时跑通一个测试点位JeasyOpc二十行代码就搞定JoPC光写ConnectionInformation就要好几行。但正式项目的点位表、断线重连、日志规范这些需求一上来JeasyOpc的封装反而成了束缚很多底层能力要自己补齐。所以我的选型建议很明确验证用JeasyOpc上线用JoPC。5. 真实环境下的故障排查链路权限错误、位数不匹配、断线重连5.1 0x80070005访问拒绝的排查过程这个错误在OPC DA对接里出现频率最高。我遇到过一个典型场景从Java代码连接OPC Server报0x80070005但同一个机器上用OPC自带的Client工具又能连上。排查链路是这样的先在OPC Server机器上用dcomcnfg检查DCOM配置重点看“启动和激活权限”和“访问权限”是否包含当前Java进程使用的Windows账户。这里有个容易被忽略的点如果你的Java进程跑在Windows服务里服务登录账户和当前登录用户不是同一个需要把服务账户加进去。确认客户端机器的Windows账户能访问Server机器。最简单的方法是在客户端机器上用net use \\服务器IP命令测试共享权限能通再继续。最后检查“标识”选项卡“交互式用户”在Server没有用户登录时可能无法启动很多工控机重启后停在登录界面这时候DCOM连接就会失败。这种情况改成“指定用户”填一个开机自动登录的账户问题就解决了。改完DCOM配置不需要重启机器但最好把OPC Server进程和服务重启一次让新配置生效。5.2 Class not registered与32/64位匹配另一种高频报错是Class not registered或者REGDB_E_CLASSNOTREG。先检查CLSID和ProgID有没有写错这个用注册表核对即可。如果确认没写错那很可能遇到了位数不匹配。Windows下COM注册表是分32位和64位的。64位系统上32位组件注册在HKEY_CLASSES_ROOT\WOW6432Node\CLSID64位注册在HKEY_CLASSES_ROOT\CLSID。Java进程如果是32位的它只能看到32位的COM组件注册信息你去连一个64位的OPC Server就会报Class not registered。判断Java进程位数很简单命令行执行java -version输出里带64-Bit就是64位否则就是32位。遇到这种问题统一让Java进程位数和OPC Server的COM组件位数一致。我踩过的是64位JDK去连32位的老版组态软件OPC Server换成32位JDK就好了。5.3 断线重连与点位数量性能工业现场网络环境没那么稳定交换机重启、网线松动、工控机丢包都会导致OPC连接断开。生产级代码一定要做断线重连。JeasyOpc里最朴实有效的做法是定时检测用ping()判断连接是否还活着断了就重建组和项ScheduledExecutorService scheduler Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleWithFixedDelay(() - { try { if (!opc.ping()) { // ping失败说明连接已断开需要重新addGroup并connect opc.addGroup(group); opc.connect(); } } catch (Exception e) { e.printStackTrace(); } }, 5, 5, TimeUnit.SECONDS);JoPC则可以用连接状态监听断开时收到通知后重新connect()。重连逻辑里有个细节OPC Server重连后原先创建的Group和Item通常需要重新创建不能复用旧对象。我见过不少人重连后继续用旧的Item读结果偶发读不到数据。点位数量方面同步读是串行调用每读一个点就是一次远程COM调用。点位在100个以下同步读还能接受到500个以上一轮轮询可能要几秒甚至十几秒这时候必须用回调模式让Server按更新速率主动推送。我之前一个项目从同步读切到回调后吞吐提升了近十倍但服务端配置的更新速率也不要太猛实测下来100ms的更新速率对Kepware这类Server压力不小一般设备数据500ms足够了。断线重连后建议先重新订阅回调组再开始业务逻辑否则存在一个数据空缺窗口缺的数据不会补回来。想要补数要么从Server端历史库补要么在应用侧做标记等重连后从设备端重新取一次快照。5.4 日志和trace开关是排查利器最后把调试经验一并说了。JeasyOpc的JOpc.setTrace(true)开启了会有大量COM层日志打在控制台或日志文件里连接失败的原因一般都能看出来。JoPC如果没有现成trace开关可以用System.setProperty(org.openscada.opc.lib.trace, true)或者直接在日志框架里把org.openscada包的级别调到DEBUG。有些问题日志看不出来尤其是DCOM连接超时这种我会在客户端机器上用Wireshark抓一下网络包过滤TCP端口135能看到DCOM的RPC调用是否成功连接被拒绝还是超时一目了然。这个方法在跨网段部署时特别有用。我在实际项目中还养成了一个习惯不管用哪个库都会先写一个最小可运行类把OPC Server的连通性验证通过再加业务逻辑。这个类只做连接、建组、读一个点当探针用。线上出问题先用这个探针类验证网络和DCOM权限如果能连通再查业务代码能非常快速地把问题边界切清楚。工业环境里网络和权限的意外情况比代码本身多得多把这层打通后面就是纯粹的Java业务了。本文还有配套的精品资源点击获取
返回列表