简介:面向Java开发者的工业数据对接资料包,围绕JOPC与JeasyOPC两套客户端API,讲解如何让Java程序穿透COM层直连OPC Server,并完成建连、浏览组项、订阅通知、读写数据与断开连接。适合需要将MES、SCADA等业务系统与PLC/DCS等设备打通的工程师,也适合刚接触工业协议的Java开发者参考。资源共860个文件,压缩包约1.99MB;除Java源码、class编译文件、jar依赖外,还包含Delphi工程文件、配置信息与SVN元数据,可看到跨语言调试与版本管理痕迹,便于研究项目落地全过程。已有1644人学习浏览。内含可直接运行的典型示例类,并针对内存泄漏、数据类型转换、订阅事件等难点给出可运行项目结构,帮助读者快速搭建基于JeasyOPC的稳健OPC客户端,缩短工业协议集成周期。
1. Java 连接 OPC Server 不是玄学:为什么绕不开 JOpc 和 JeasyOpc
Java 要连 OPC Server 获取实时数据,绕不开的库就是 JOpc 和 JeasyOpc。这不是什么冷门需求,工控数据采集、MES 对接、设备状态监控,凡是老车间上位机要跟 PLC 侧的数据打交道,八成最后都落到这两个库上。它们解决的是同一个问题:让 Java 程序作为 OPC DA 客户端,从 Windows 上的 OPC Server 里按点位名读到数值、质量戳和时间戳。适合谁?适合手里有一台跑着 OPC Server 的 Windows 机器、需要用 Java 做数据采集或二次开发的工程师。这篇笔记不绕弯子,直接把连接方式、参数和坑都拆开讲。
2. 先搞懂 OPC DA 的“黑匣子”:COM/DCOM 与两种 Java 库的选型逻辑
2.1 OPC DA 和 OPC UA 的区别,以及为什么老设备只有 DA
OPC DA 是 OPC 规范里最老的一代,底层是 Windows 的 COM/DCOM 技术。它走的是局域网内的进程间通信,Server 和 Client 都跑在 Windows 上,靠系统组件注册表找到彼此。OPC UA 是后来的跨平台标准,不依赖 Windows COM,但现场大量老设备、老上位机系统当年只做了 DA 接口,改造升级成本高,所以 DA 至今没有退场。Java 默认没有 COM 通信能力,所以你不能像 C# 那样直接用系统 API,必须借助桥接库。JOpc 和 JeasyOpc 就是干这个的:它们封装了 COM 交互细节,让 Java 代码以 native 调用的方式访问 OPC Server。
2.2 JOpc 与 JeasyOpc 的定位差异与选型
JOpc 是偏重量级的封装,功能覆盖更完整,支持浏览服务器节点、批量读、异步回调,适合做完整的数据采集服务。JeasyOpc 更轻,核心就是连接、同步读、写,适合快速搭一个 demo 或者点位少的项目。两者都需要下载对应的 jar 包和 native 库文件。选型建议很简单:点位多、要长期跑、有回调需求的用 JOpc;只想赶紧把数据读出来验证流程的用 JeasyOpc。它们是同一个问题的两种解法,不存在谁完全替代谁。
2.3 环境准备:系统要求、必要组件、调用方式的取舍
这两个库的前提是 Java 程序必须运行在 Windows 上,且 OPC Server 和客户端在同一台机器或同一域内。远程访问 DA 需要配置 DCOM 权限,后面第 5 章专门讲。本机测试最简单,先装好 OPC Server 模拟器,再确认 Java 是 32 位还是 64 位。这里有个硬性要求:JOpc 的 native 库通常只提供 32 位版本,如果你用的是 64 位 JDK,会直接报加载失败。我一般会在项目里同时保留两个 JDK,用 32 位版跑采集程序,64 位版跑其他业务。
3. JOpc 实战:连接、读点、释放连接
3.1 最小可用 demo:连接与读点
先写个最简单的 demo:连接本机 OPC Server,读取一个点位。以模拟器默认提供的“随机数”点位为例,完整代码如下:
import org.openscada.opc.lib.da.*; import org.openscada.opc.lib.da.browser.Branch; import org.openscada.opc.lib.da.browser.Leaf; import org.jinterop.dcom.common.JIException; import org.jinterop.dcom.core.JIStrings; public class JopcDemo { public static void main(String[] args) throws Exception { // 1. 连接信息 String host = "localhost"; String domain = ""; String user = "administrator"; String password = "123456"; String progId = "YourOpcServer.ProgId"; // 替换为实际 OPC Server 的 ProgID // 2. 建立连接 Server server = new Server(progId, host, domain, user, password); server.connect(); // 3. 读取点位 Item item = server.getService().addItem("Random.Int1", true); Integer value = (Integer) item.read(false).getValue(); System.out.println("Current value: " + value); // 4. 断开 server.dispose(); } }这段代码的核心是Server对象的四个参数:host 是 OPC Server 所在机器,domain 和 user 是 Windows 登录凭据,progId 是 OPC Server 在系统里注册的编程标识符。addItem的第二个参数表示是否同步读取,异步采集场景下可以传 false。read(false)里那个参数是是否返回缓存值,false 表示强制读设备实时值。第 4 步的dispose()必须执行,否则 COM 连接不会释放,调试时会看到进程一直占着 OPC Server。
3.2 参数说明:服务器名、点位路径、超时
OPC Server 的 progId 可以在系统的组件服务里查到,也可以问现场运维要。点位路径不是随便写的,它对应 OPC Server 内部的地址空间,模拟器通常在界面上就能看到树形结构。比如“Random.Int1”就是根节点下的一个分支和叶子的组合。连接超时一般默认 10 秒,如果现场网络慢,可以在Server构造后手动调大:
server.setTimeout(30000); // 30 秒超时,单位毫秒如果是远程连接,这里的 host 要写 IP,而且前面说的 DCOM 权限必须配好,否则报错不是超时就是拒绝访问。点位路径写错的话,addItem会抛异常,但异常信息不一定明确,常见的是 “Unknown item” 或 “HRESULT: 0x80040000”。这个错误只能靠核对点位名解决,没有捷径。
3.3 连续采集与断线处理
实际采集不会只读一次,通常要轮询。JOpc 提供了现成的Polling接口,但更常见的做法是自己起一个定时任务:
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(() -> { try { Item item = server.getService().addItem("Random.Int1", false); Object value = item.read(true).getValue(); System.out.println(System.currentTimeMillis() + " -> " + value); } catch (JIException e) { e.printStackTrace(); // 断线重连逻辑 } }, 0, 1000, TimeUnit.MILLISECONDS);这里的问题在于addItem不是线程安全的,多线程轮询同一个 Server 实例容易出并发问题。我一般会单线程轮询,或者按点位分多个 Server 实例。断线重连不能只靠 catch,要监听连接状态,JOpc 有ServerState回调,但手写的话就是 catch 到异常后调server.dispose()再重新connect(),注意重连间隔至少要 3 秒以上,否则 DCOM 的旧连接没释放干净,会越连越慢。
4. JeasyOpc 实战:另一种连法的对比与迁移
4.1 JeasyOpc 连接流程
JeasyOpc 的用法更直白,核心类就三个:OPCConnection、Group、OPCItem。连接本机代码:
import javafish.toolopc.opc.OPCConnection; import javafish.toolopc.opc.group.Group; import javafish.toolopc.opc.item.OPCItem; public class JeasyDemo { public static void main(String[] args) throws Exception { // 1. 建立连接 OPCConnection connection = new OPCConnection("YourOpcServer.ProgId", "localhost"); connection.connect(); // 2. 创建组 Group group = new Group("group1", true, 500, 0.0f); connection.addGroup(group); // 3. 添加点位 OPCItem item = new OPCItem("Random.Int1", true, ""); group.addItem(item); connection.addItem(group, item); // 4. 同步读 connection.syncRead(group, item); System.out.println("Value: " + item.getValue()); // 5. 断开 connection.disconnect(); } }这个库的直观之处是它的 API 命名跟 OPC 官方概念一一对应:Group就是 OPC DA 里的组,OPCItem是点位。创建组时的500是组更新速率,单位毫秒,意思是 OPC Server 按这个周期向客户端推送数据变化。0.0f是死区百分比,0 表示所有变化都上报,现场一般设 1% 左右,避免频繁抖动浪费带宽。
4.2 两种库的代码差异与选型结论
从代码量看,JeasyOpc 更少,逻辑也更直白,适合点位少、只需要定时读的项目。JOpc 多了一层Server/Service抽象,功能多但学习成本高。迁移角度,两者的点位路径写法基本一致,都是 OPC Server 树形结构里的字符串地址,所以换库时点位配置可以直接复用。如果项目要上生产环境、点位超过几百个、还要做异步订阅,选 JOpc;如果就是做个工具脚本、周期读十几个点位,JeasyOpc 半天就能跑通。
5. 避坑:DCOM、位数、防火墙,OPC DA 三个老坑
5.1 现象:连接本机成功,远程连接一直超时
原因:远程 OPC DA 走 DCOM 协议,Windows 默认禁止远程 COM 调用。解决分三步:在 OPC Server 机器上打开 dcomcnfg,找到 OPC Server 的 DCOM 配置,把“标识”标签页设为“交互式用户”;在“安全”标签页给 Everyone 加上启动和访问权限;最后在防火墙里放行 TCP 135 端口,再加上 OPC Server 自己的程序端口。做完这三步大多数情况能通,偶尔还需要把客户端机器的用户密码也加到 Server 机器的同一域或同一工作组里。
5.2 现象:加载 native 库失败,提示找不到 jopc.dll 或类似错误
原因:绝大多数 OPC DA 桥接库的 DLL 只编译了 32 位,64 位 JDK 无法加载。解决:强制使用 32 位 JDK 运行采集程序,同时确认 IDE 里项目的编译目标也是 32 位。这个坑最容易翻车,因为它不在代码层面,在环境层面。我遇到过有人折腾一整天,最后把 JDK 换成 32 位直接好了。
5.3 现象:点位能读,但读出来的值永远是 null
原因:点位路径存在,但数据类型不匹配。OPC Server 里的点可能是字符串或浮点,你用 Integer 去接就会拿到 null。解决:改用getValueAsString()或统一用Object接收,打印出getClass().getName()看实际类型。JOpc 和 JeasyOpc 都有这个情况,不是库的问题,是点位类型映射的问题。
5.4 现象:程序跑一段时间后连接断掉,重连报错 “access denied”
原因:DCOM 的会话超时机制,长时间无操作后服务器回收了会话,重新连接时旧会话还没完全释放。解决:给采集程序加心跳,每 30 秒读一次任意点位保持会话活跃;重连前先sleep(5000)再重试,连续三次失败后彻底重启采集进程。这个策略救过我好几次。
6. 进阶:用模拟 OPC Server 练一遍读写与重连习惯
生产环境的 OPC Server 不会让你随便练手,我一般先在本地装一个模拟 OPC Server,把整个流程跑通再上现场。模拟器里可以建任意点位、改变数值生成方式、模拟断线,这些正好压测客户端代码的重连逻辑。具体做法是:模拟器建一组随机数点位,客户端写一个 10 分钟的连续采集程序,期间手动停掉 OPC Server 再启动,观察客户端是否能在 5 秒内自动恢复连接。这个测试能暴露大部分 DCOM 会话问题。
如果你是第一次接触这俩库,建议按这个顺序走一遍:先跑 JeasyOpc 的最小 demo,确认模拟器连接无误;再用 JOpc 写一个轮询 5 个点位的采集程序;最后加上断线重连和日志输出,连续跑过夜。每一步都确认环境正常了再进下一步。我的习惯是每次改动依赖库版本或 JDK 位数后,都强制走一遍“读一个点位 + 断线重连”的 smoke test,这个动作帮我省掉了无数个排查“昨天还好好的为什么今天连不上”的下午。希望这篇笔记能帮你在 OPC DA 这条老路上少踩几个坑。
本文还有配套的精品资源,点击获取