java 实现opc代码实现,附加问题解决方法和错误编码详解

在使用opc之前我们先了解一下什么是opc,首先OPC包含三个概念模型:

  • OPC Server
  • OPC Group(注意这个加粗!!!)

  • OPC Item

关于这三个概念模型具体含义,我就不一一赘述了,大家可点进下面的连接查看
点点击击

首先,我们的需求是,用Java写一个OPC客户端程序,定时从OPC服务读数据,那么我们来看下网上的DEMO咋写的:

 
  1. public static void test() throws Exception {

  2. final ConnectionInformation ci = new ConnectionInformation();

  3. ci.setHost("10.211.55.4");

  4. ci.setUser("OPCUser");

  5. ci.setPassword("opcuser");

  6. ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");

  7. Item item = null;

  8. Server server = new Server(ci, null);

  9. try {

  10. server.connect();

  11. Group group = server.addGroup();

  12. item = group.addItem("tongdao.tag1.aaa");

  13. System.out.println("读取到的值为:" + getVal(item.read(true).getValue()));

  14. server.disconnect();

  15. } catch (Exception e) {

  16. e.printStackTrace();

  17. }

  18. }

我们简单理解下,和大部分通信客户端一个步骤:建立通道连接,传参,取数据,关闭连接

如果我们需要取多个点号的数据怎么办?
在外面套一层for循环对吧(没错,我有个朋友也是这么想的)
那么就有了如下写法:

 
  1. public static void test() throws Exception {

  2. final ConnectionInformation ci = new ConnectionInformation();

  3. ci.setHost("10.211.55.4");

  4. ci.setUser("OPCUser");

  5. ci.setPassword("opcuser");

  6. ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");

  7. Item item = null;

  8. Server server = new Server(ci, null);

  9. List<String> itemIdList = new ArrayList<>(); // 假设这里面有数据

  10. try {

  11. server.connect();

  12. for(String itemId : itemIdList) {

  13. Group group = server.addGroup();

  14. item = group.addItem("tongdao.tag1.aaa");

  15. System.out.println("读取到的值为:" + getVal(item.read(true).getValue()));

  16. }

  17. server.disconnect();

  18. } catch (Exception e) {

  19. e.printStackTrace();

  20. }

  21. }

写完后信心满满,仿佛屈屈OPC,不过如此!
到了测试环节,我们真实环境大概两万个点位,数据量也不是很大,不是分分钟测试成功?
结果是:人家的OPC Server服务被(我的一个朋友)成功搞挂了;

看来不能一次性取这么多啊,这个OPC。。。。。8太行啊
那我们先取1000个试试水,看看性能究竟如何

代码主要逻辑不变,我们把参数长度设为1000

 
  1. List<String> itemIdList = new ArrayList<>(); // 假设这里面有数据

  2. itemIdList = itemIdList.subList(0, 1000); //只查1000条数据,测测性能

执行完时间大概在8秒左右

嘶~倒吸一口凉气,这1000条就要8秒,那我2w条不得 8 * 20 = 160s?
两分多钟,也还行,,,,
但是!作为一枚专业及严谨的程序员,怎么能忍受得了2分多钟的时长!不行!绝对不行!

此时我那个朋友萌生出了另一套方案,单线程1000条8s左右,那我开双线程分别查询1000条是不是也是8s左右了捏?这样折合下来能节约一半的时间嘛,而且可以的话我们可以多建几个线程去跑,这样时间又会以指数级下降

于是有了以下这段代码:

 
  1. package com.oukong.framework;

  2. import org.jinterop.dcom.common.JIErrorCodes;

  3. import org.jinterop.dcom.common.JIException;

  4. import org.jinterop.dcom.core.JIVariant;

  5. import org.openscada.opc.lib.common.ConnectionInformation;

  6. import org.openscada.opc.lib.da.*;

  7. import java.util.*;

  8. import java.util.concurrent.CountDownLatch;

  9. public class OpcTest3 {

  10. public static void main(String[] args) throws Exception {

  11. test();

  12. }

  13. public static void test() throws Exception {

  14. final ConnectionInformation ci = new ConnectionInformation();

  15. ci.setHost("10.211.55.4");

  16. ci.setUser("OPCUser");

  17. ci.setPassword("opcuser");

  18. ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");

  19. Item item = null;

  20. Server server = new Server(ci, null);

  21. List<String> itemIdList = new ArrayList<>(); // 假设这里面有数据

  22. List<String> itemList1 = itemIdList.subList(0, 1000);

  23. List<String> itemList2 = itemIdList.subList(1000, 2000);

  24. Map<String, Object> result = new HashMap<>();

  25. try {

  26. server.connect();

  27. CountDownLatch countDownLatch = new CountDownLatch(2); //线程计数器

  28. OpcThread1 thread = new OpcThread1(server, itemList1, countDownLatch, result);

  29. OpcThread1 thread2 = new OpcThread1(server, itemList2, countDownLatch, result);

  30. thread.start();

  31. thread2.start();

  32. countDownLatch.await(); //等待两个线程都执行完成

  33. server.disconnect();

  34. } catch (Exception e) {

  35. e.printStackTrace();

  36. }

  37. }

  38. }

  39. class OpcThread1 extends Thread {

  40. private List<String> itemList;

  41. private Server server;

  42. private CountDownLatch countDownLatch;

  43. private Map<String, Object> result;

  44. public OpcThread1(Server server, List<String> itemList, CountDownLatch countDownLatch,

  45. Map<String, Object> result) {

  46. this.server = server;

  47. this.itemList = itemList;

  48. this.countDownLatch = countDownLatch;

  49. this.result = result;

  50. }

  51. @Override

  52. public void run() {

  53. Group group = null;

  54. try {

  55. server.connect();

  56. for(String itemId : itemList) {

  57. Group group = server.addGroup();

  58. Item item = group.addItem("tongdao.tag1.aaa");

  59. System.out.println("读取到的值为:" + getVal(item.read(true).getValue()));

  60. }

  61. countDownLatch.countDown();

  62. } catch (Exception e) {

  63. e.printStackTrace();

  64. }

  65. }

  66. }

然后执行结果不出所料,和预想的时间相差不大,但是,我们忽略了一个问题,这个OPC服务的吞吐量8太行,真怕一不注意就把服务给搞挂了,所以,该方法虽然表面上可行,但是,,,,下流,,,,

难道真的没别的方法了吗?
关于上面提到的组,没有利用空间的吗?

于是我那个朋友翻了Group对象的源码,发现提供了这么个东东

image.png

该对象提供了添加多个Item的方法!也就意味着我们可以一次性传多个参数进行请求,然后回给我们返回一个集合!
于是我们有了如下代码:

 
  1. @Override

  2. public void run() {

  3. Group group = null;

  4. try {

  5. group = server.addGroup();

  6. String[] items = itemList.toArray(new String[]{});

  7. Map<String, Item> itemResult = group.addItems(items);

  8. for(String key : itemResult.keySet()) {

  9. Item itemMap = itemResult.get(key);

  10. result.put(key, getVal(itemMap.read(true).getValue()));

  11. }

  12. countDownLatch.countDown();

  13. } catch (Exception e) {

  14. e.printStackTrace();

  15. }

  16. }

上面的代码中我们用一个map将返回值给存了起来
再测一下!
结果:单线程情况下,请求1000条数据,耗时3秒左右
emmm....虽然进步了,但是1000条还是要3秒,2w条还是要1分钟左右,这能忍?不能!

于是我还想再找找有没有别的我没注意到的地方了,然后就发下了一行比较诡异的代码:

image.png

正常我们取值不是直接getValue()就好了?这个为什么还要read一下?

然后发现真正从客户端读取数据的就是这一行,我那个朋友之前误以为这个玩意儿就是取完值的集合了

image.png

但事实是,我们虽然分了组,但是取数据的时候还是一个item一个item地取,所以会如此之慢,,,,然后我又扒了一遍group的源码,又发现了这个东东:

image.png


看清没,我们之前是通过item去read值,但事实是,人家group本身就有一个read,而且将真正的值的集合返回给了我们,再稍微推敲一下,是不是这个组建好之后,我们可以取多次数据了。。。。

知道了这个方法,稍微改造了下代码,我批量穿参时候,也批量取,于是就有了如下代码!!!!(此处应有闪光特效)

 
  1. package com.oukong.framework;

  2. import org.jinterop.dcom.common.JIErrorCodes;

  3. import org.jinterop.dcom.common.JIException;

  4. import org.jinterop.dcom.core.JIVariant;

  5. import org.openscada.opc.dcom.da.OPCSERVERSTATE;

  6. import org.openscada.opc.lib.common.AlreadyConnectedException;

  7. import org.openscada.opc.lib.common.ConnectionInformation;

  8. import org.openscada.opc.lib.common.NotConnectedException;

  9. import org.openscada.opc.lib.da.*;

  10. import java.io.*;

  11. import java.net.UnknownHostException;

  12. import java.util.*;

  13. import java.util.concurrent.CountDownLatch;

  14. import java.util.concurrent.Executors;

  15. public class OpcDaTest2 {

  16. public static void test(List<String> itemList) {

  17. List<String> itemList1 = itemList.subList(0, 500);

  18. List<String> itemList2 = itemList.subList(1000, 2000);

  19. final ConnectionInformation ci = new ConnectionInformation();

  20. ci.setHost("10.10.1.13"); // KEPServer服务器所在IP

  21. ci.setDomain(""); // 域 为空

  22. ci.setUser("OPCuser");

  23. ci.setPassword("Sa2022");

  24. ci.setClsid("4B12BF21-3C60-4C48-A47F-E5F1E3BCFD34"); // OPCServer的注册表ID,可以在“组件服务”中查到

  25. Item item = null;

  26. Server server = new Server(ci, null);

  27. Map<String, Object> result = new HashMap<>();

  28. try {

  29. server.connect();

  30. long start = System.currentTimeMillis();

  31. CountDownLatch countDownLatch = new CountDownLatch(1);

  32. OpcThread thread = new OpcThread(server, itemList1, countDownLatch, result);

  33. thread.start();

  34. countDownLatch.await();

  35. long end = System.currentTimeMillis();

  36. System.out.println("totalSize: " + result.size() + "\tuse :" + (end - start) + "ms");

  37. } catch (Exception e) {

  38. e.printStackTrace();

  39. }

  40. }

  41. }

  42. class OpcThread extends Thread {

  43. private List<String> itemList;

  44. private Server server;

  45. private CountDownLatch countDownLatch;

  46. private Map<String, Object> result;

  47. public OpcThread(Server server, List<String> itemList, CountDownLatch countDownLatch,

  48. Map<String, Object> result) {

  49. this.server = server;

  50. this.itemList = itemList;

  51. this.countDownLatch = countDownLatch;

  52. this.result = result;

  53. }

  54. @Override

  55. public void run() {

  56. Group group = null;

  57. try {

  58. // 建组

  59. long s = System.currentTimeMillis();

  60. group = server.addGroup();

  61. String[] items = itemList.toArray(new String[]{});

  62. Map<String, Item> itemResult = group.addItems(items);

  63. System.out.println(itemResult.size());

  64. long e = System.currentTimeMillis();

  65. System.out.println("建组耗时:" + (e - s));

  66. //第一次取数据

  67. long start = System.currentTimeMillis();

  68. Set itemSet = new HashSet(itemResult.values());

  69. Item[] itemArr = new Item[itemSet.size()];

  70. itemSet.toArray(itemArr);

  71. Map<Item, ItemState> resultMap = group.read(true, itemArr);

  72. for(Item key : resultMap.keySet()) {

  73. ItemState itemMap = resultMap.get(key);

  74. result.put(key.getId(), getVal(itemMap.getValue()));

  75. }

  76. long end = System.currentTimeMillis();

  77. System.out.println("group1 totalSize1 : " + itemResult.size() + "\tuse :" + (end - start) + "ms");

  78. //第二次取数据

  79. long start2 = System.currentTimeMillis();

  80. Map<Item, ItemState> resultMap2 = group.read(true, itemArr);

  81. for(Item key : resultMap2.keySet()) {

  82. ItemState itemMap = resultMap2.get(key);

  83. result.put(key.getId(), getVal(itemMap.getValue()));

  84. }

  85. long end2 = System.currentTimeMillis();

  86. System.out.println("group1 totalSize2 : " + resultMap2.size() + "\tuse :" + (end2 - start2) + "ms");

  87. countDownLatch.countDown();

  88. } catch (Exception e) {

  89. e.printStackTrace();

  90. }

  91. }

  92. }

这下我们彻底将组利用了起来,执行结果如下:

 
  1. 建组耗时:177214

  2. group1 totalSize1 : 24000 use :6467ms

  3. group1 totalSize2 : 24000 use :1526ms

我们将最初3分钟的时间,优化到了6秒钟,就问6不6?

最后附上完整的依赖和代码

maven

 
  1. <!--utgard -->

  2. <dependency>

  3. <groupId>org.openscada.utgard</groupId>

  4. <artifactId>org.openscada.opc.lib</artifactId>

  5. <version>1.5.0</version>

  6. <exclusions>

  7. <exclusion>

  8. <groupId>org.bouncycastle</groupId>

  9. <artifactId>bcprov-jdk15on</artifactId>

  10. </exclusion>

  11. </exclusions>

  12. </dependency>

  13. <dependency>

  14. <groupId>org.bouncycastle</groupId>

  15. <artifactId>bcprov-jdk15on</artifactId>

  16. <version>1.65</version>

  17. </dependency>

  18. <dependency>

  19. <groupId>org.openscada.utgard</groupId>

  20. <artifactId>org.openscada.opc.dcom</artifactId>

  21. <version>1.5.0</version>

  22. </dependency>

OPC客户端

 
  1. import lombok.extern.slf4j.Slf4j;

  2. import org.jinterop.dcom.common.JIErrorCodes;

  3. import org.jinterop.dcom.common.JIException;

  4. import org.jinterop.dcom.core.JIVariant;

  5. import org.openscada.opc.dcom.da.OPCSERVERSTATE;

  6. import org.openscada.opc.lib.common.AlreadyConnectedException;

  7. import org.openscada.opc.lib.common.ConnectionInformation;

  8. import org.openscada.opc.lib.common.NotConnectedException;

  9. import org.openscada.opc.lib.da.*;

  10. import java.net.UnknownHostException;

  11. import java.util.*;

  12. /**

  13. * @Auther: 夏

  14. * @DATE: 2022/6/8 14:58

  15. * @Description: opc da客户端

  16. */

  17. @Slf4j

  18. public class OpcDAClient {

  19. private String host;

  20. private String user;

  21. private String password;

  22. private String clsId;

  23. private Server server;

  24. private String bakHost;

  25. private Integer groupCount;

  26. private Group group = null;

  27. private Map<String, Item> groupItems = null;

  28. /**

  29. * 初始化连接信息

  30. * @param host

  31. * @param user

  32. * @param password

  33. * @param clsId

  34. */

  35. public OpcDAClient(String host, String user, String password, String clsId, Integer groupCount) {

  36. this.host = host;

  37. this.user = user;

  38. this.password = password;

  39. this.clsId = clsId;

  40. this.groupCount = groupCount;

  41. }

  42. /**

  43. * 设置备用服务地址

  44. * @param bakHost

  45. */

  46. public void setBakHost(String bakHost) {

  47. this.bakHost = bakHost;

  48. }

  49. /**

  50. * 创建连接

  51. */

  52. public void connect() {

  53. if(server.getServerState() != null && server.getServerState().getServerState().equals(OPCSERVERSTATE.OPC_STATUS_RUNNING)) {

  54. return;

  55. }

  56. final ConnectionInformation ci = new ConnectionInformation();

  57. ci.setHost(host);

  58. ci.setDomain(""); // 域 为空

  59. ci.setUser(user);

  60. ci.setPassword(password);

  61. ci.setClsid(clsId);

  62. server = new Server(ci, null);

  63. try {

  64. server.connect();

  65. } catch (UnknownHostException e) {

  66. e.printStackTrace();

  67. log.error("opc 地址错误:", e);

  68. } catch (JIException e) {

  69. e.printStackTrace();

  70. log.error("opc 连接失败:", e);

  71. log.info("开始连接备用服务...");

  72. try {

  73. ci.setHost(bakHost);

  74. server = new Server(ci, null);

  75. server.connect();

  76. } catch (Exception e2) {

  77. log.error("备用服务连接失败:", e2);

  78. }

  79. } catch (AlreadyConnectedException e) {

  80. e.printStackTrace();

  81. log.error("opc 已连接:", e);

  82. }

  83. log.info("OPC Server connect success...");

  84. }

  85. /**

  86. * 根据地址获取数据

  87. * @param itemId

  88. * @return

  89. */

  90. public Object getItemValue(String itemId) {

  91. try {

  92. Group group = server.addGroup();

  93. Item item = group.addItem(itemId);

  94. return getVal(item.read(true).getValue());

  95. } catch (Exception e) {

  96. e.printStackTrace();

  97. log.error("获取数据异常 itemId:{}", itemId, e);

  98. }

  99. return null;

  100. }

  101. /**

  102. * 获取多组数据

  103. * @param itemIdList

  104. * @return

  105. */

  106. public Map<String, Object> getItemValues(List<String> itemIdList) {

  107. Map<String, Object> result = new HashMap<>();

  108. try {

  109. if(groupItems == null || group == null) {

  110. log.info("开始建组...");

  111. group = server.addGroup();

  112. String[] items = itemIdList.toArray(new String[]{});

  113. groupItems = group.addItems(items);

  114. log.info("组建完成,开始查询数据...");

  115. }

  116. Set itemSet = new HashSet(groupItems.values());

  117. Item[] itemArr = new Item[itemSet.size()];

  118. itemSet.toArray(itemArr);

  119. Map<Item, ItemState> resultMap = group.read(true, itemArr);

  120. log.info("数据获取完成:{}条", resultMap.size());

  121. for(Item item : resultMap.keySet()) {

  122. ItemState itemMap = resultMap.get(item);

  123. result.put(item.getId(), getVal(itemMap.getValue()));

  124. }

  125. } catch (Exception e) {

  126. e.printStackTrace();

  127. log.error("批量获取数据异常:", e);

  128. }

  129. return result;

  130. }

  131. /**

  132. * 获取value

  133. * @param var

  134. * @return

  135. * @throws JIException

  136. */

  137. private static Object getVal(JIVariant var) throws JIException {

  138. Object value;

  139. int type = var.getType();

  140. switch (type) {

  141. case JIVariant.VT_I2:

  142. value = var.getObjectAsShort();

  143. break;

  144. case JIVariant.VT_I4:

  145. value = var.getObjectAsInt();

  146. break;

  147. case JIVariant.VT_I8:

  148. value = var.getObjectAsLong();

  149. break;

  150. case JIVariant.VT_R4:

  151. value = var.getObjectAsFloat();

  152. break;

  153. case JIVariant.VT_R8:

  154. value = var.getObjectAsDouble();

  155. break;

  156. case JIVariant.VT_BSTR:

  157. value = var.getObjectAsString2();

  158. break;

  159. case JIVariant.VT_BOOL:

  160. value = var.getObjectAsBoolean();

  161. break;

  162. case JIVariant.VT_UI2:

  163. case JIVariant.VT_UI4:

  164. value = var.getObjectAsUnsigned().getValue();

  165. break;

  166. case JIVariant.VT_EMPTY:

  167. throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is Empty.");

  168. case JIVariant.VT_NULL:

  169. throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is null.");

  170. default:

  171. throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Unknown Type.");

  172. }

  173. return value;

  174. }

  175. /**

  176. * 将一组数据平均分成n组

  177. *

  178. * @param source 要分组的数据源

  179. * @param n 平均分成n组

  180. * @param <T>

  181. * @return

  182. */

  183. private static <T> List<List<T>> averageAssign(List<T> source, int n) {

  184. List<List<T>> result = new ArrayList<List<T>>();

  185. int remainder = source.size() % n; //(先计算出余数)

  186. int number = source.size() / n; //然后是商

  187. int offset = 0;//偏移量

  188. for (int i = 0; i < n; i++) {

  189. List<T> value = null;

  190. if (remainder > 0) {

  191. value = source.subList(i * number + offset, (i + 1) * number + offset + 1);

  192. remainder --;

  193. offset++;

  194. } else {

  195. value = source.subList(i * number + offset, (i + 1) * number + offset);

  196. }

  197. result.add(value);

  198. }

  199. return result;

  200. }

  201. /**

  202. * 关闭连接

  203. */

  204. public void disconnect() {

  205. server.disconnect();

  206. if (null == server.getServerState()) {

  207. log.info("OPC Server Disconnect...");

  208. }

  209. }

  210. }

最后,如果连接服务端有报错,可以到一下连接查看配置步骤和相关的错误编码

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java实现OPC UA的步骤如下: 第一步,首先需要引入OPC UA的Java开发库。目前市场上有很多供应商提供的OPC UA开发库,选择适合自己的库进行引用。这些库通常包含了OPC UA的客户端和服务器代码。 第二步,编写OPC UA客户端代码OPC UA客户端主要用于与OPC UA服务器进行通信。通过在Java程序中引入OPC UA库提供的相关类和接口,可以创建一个OPC UA客户端实例。然后,通过该实例可以实现与服务器的连接、节点浏览、读写数据等功能。 第三步,编写OPC UA服务器代码OPC UA服务器主要用于提供OPC UA服务,允许客户端与其进行通信。类似地,通过引入OPC UA库提供的相关类和接口,可以创建一个OPC UA服务器实例。然后,可以定义自己的节点和数据模型,并实现相应的方法用于处理客户端请求。 第四步,配置OPC UA服务器。将OPC UA服务器配置为可以被客户端访问,可以通过修改配置文件或者在代码中设置服务器的IP地址和端口号来实现。 第五步,测试和调试。在完成客户端和服务器的编写后,可以通过启动客户端和服务器来对其进行测试和调试。通过客户端可以访问服务器的节点并读写数据,通过服务器可以查看客户端请求并进行相应的响应。 总结起来,Java实现OPC UA的过程涉及到引入OPC UA库、编写客户端和服务器代码、配置服务器以及测试和调试。通过这些步骤,可以实现Java程序与OPC UA服务器的交互,实现数据的读写和通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值