在使用opc之前我们先了解一下什么是opc,首先OPC包含三个概念模型:
- OPC Server
-
OPC Group(注意这个加粗!!!)
- OPC Item
关于这三个概念模型具体含义,我就不一一赘述了,大家可点进下面的连接查看
点点击击
首先,我们的需求是,用Java写一个OPC客户端程序,定时从OPC服务读数据,那么我们来看下网上的DEMO咋写的:
-
public static void test() throws Exception {
-
final ConnectionInformation ci = new ConnectionInformation();
-
ci.setHost("10.211.55.4");
-
ci.setUser("OPCUser");
-
ci.setPassword("opcuser");
-
ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
-
Item item = null;
-
Server server = new Server(ci, null);
-
try {
-
server.connect();
-
Group group = server.addGroup();
-
item = group.addItem("tongdao.tag1.aaa");
-
System.out.println("读取到的值为:" + getVal(item.read(true).getValue()));
-
server.disconnect();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
我们简单理解下,和大部分通信客户端一个步骤:建立通道连接,传参,取数据,关闭连接
如果我们需要取多个点号的数据怎么办?
在外面套一层for循环对吧(没错,我有个朋友也是这么想的)
那么就有了如下写法:
-
public static void test() throws Exception {
-
final ConnectionInformation ci = new ConnectionInformation();
-
ci.setHost("10.211.55.4");
-
ci.setUser("OPCUser");
-
ci.setPassword("opcuser");
-
ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
-
Item item = null;
-
Server server = new Server(ci, null);
-
List<String> itemIdList = new ArrayList<>(); // 假设这里面有数据
-
try {
-
server.connect();
-
for(String itemId : itemIdList) {
-
Group group = server.addGroup();
-
item = group.addItem("tongdao.tag1.aaa");
-
System.out.println("读取到的值为:" + getVal(item.read(true).getValue()));
-
}
-
server.disconnect();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
写完后信心满满,仿佛屈屈OPC,不过如此!
到了测试环节,我们真实环境大概两万个点位,数据量也不是很大,不是分分钟测试成功?
结果是:人家的OPC Server服务被(我的一个朋友)成功搞挂了;
看来不能一次性取这么多啊,这个OPC。。。。。8太行啊
那我们先取1000个试试水,看看性能究竟如何
代码主要逻辑不变,我们把参数长度设为1000
-
List<String> itemIdList = new ArrayList<>(); // 假设这里面有数据
-
itemIdList = itemIdList.subList(0, 1000); //只查1000条数据,测测性能
执行完时间大概在8秒左右
嘶~倒吸一口凉气,这1000条就要8秒,那我2w条不得 8 * 20 = 160s?
两分多钟,也还行,,,,
但是!作为一枚专业及严谨的程序员,怎么能忍受得了2分多钟的时长!不行!绝对不行!
此时我那个朋友萌生出了另一套方案,单线程1000条8s左右,那我开双线程分别查询1000条是不是也是8s左右了捏?这样折合下来能节约一半的时间嘛,而且可以的话我们可以多建几个线程去跑,这样时间又会以指数级下降
于是有了以下这段代码:
-
package com.oukong.framework;
-
import org.jinterop.dcom.common.JIErrorCodes;
-
import org.jinterop.dcom.common.JIException;
-
import org.jinterop.dcom.core.JIVariant;
-
import org.openscada.opc.lib.common.ConnectionInformation;
-
import org.openscada.opc.lib.da.*;
-
import java.util.*;
-
import java.util.concurrent.CountDownLatch;
-
public class OpcTest3 {
-
public static void main(String[] args) throws Exception {
-
test();
-
}
-
public static void test() throws Exception {
-
final ConnectionInformation ci = new ConnectionInformation();
-
ci.setHost("10.211.55.4");
-
ci.setUser("OPCUser");
-
ci.setPassword("opcuser");
-
ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729");
-
Item item = null;
-
Server server = new Server(ci, null);
-
List<String> itemIdList = new ArrayList<>(); // 假设这里面有数据
-
List<String> itemList1 = itemIdList.subList(0, 1000);
-
List<String> itemList2 = itemIdList.subList(1000, 2000);
-
Map<String, Object> result = new HashMap<>();
-
try {
-
server.connect();
-
CountDownLatch countDownLatch = new CountDownLatch(2); //线程计数器
-
OpcThread1 thread = new OpcThread1(server, itemList1, countDownLatch, result);
-
OpcThread1 thread2 = new OpcThread1(server, itemList2, countDownLatch, result);
-
thread.start();
-
thread2.start();
-
countDownLatch.await(); //等待两个线程都执行完成
-
server.disconnect();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
class OpcThread1 extends Thread {
-
private List<String> itemList;
-
private Server server;
-
private CountDownLatch countDownLatch;
-
private Map<String, Object> result;
-
public OpcThread1(Server server, List<String> itemList, CountDownLatch countDownLatch,
-
Map<String, Object> result) {
-
this.server = server;
-
this.itemList = itemList;
-
this.countDownLatch = countDownLatch;
-
this.result = result;
-
}
-
@Override
-
public void run() {
-
Group group = null;
-
try {
-
server.connect();
-
for(String itemId : itemList) {
-
Group group = server.addGroup();
-
Item item = group.addItem("tongdao.tag1.aaa");
-
System.out.println("读取到的值为:" + getVal(item.read(true).getValue()));
-
}
-
countDownLatch.countDown();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
然后执行结果不出所料,和预想的时间相差不大,但是,我们忽略了一个问题,这个OPC服务的吞吐量8太行,真怕一不注意就把服务给搞挂了,所以,该方法虽然表面上可行,但是,,,,下流,,,,
难道真的没别的方法了吗?
关于上面提到的组,没有利用空间的吗?
于是我那个朋友翻了Group对象的源码,发现提供了这么个东东
image.png
该对象提供了添加多个Item的方法!也就意味着我们可以一次性传多个参数进行请求,然后回给我们返回一个集合!
于是我们有了如下代码:
-
@Override
-
public void run() {
-
Group group = null;
-
try {
-
group = server.addGroup();
-
String[] items = itemList.toArray(new String[]{});
-
Map<String, Item> itemResult = group.addItems(items);
-
for(String key : itemResult.keySet()) {
-
Item itemMap = itemResult.get(key);
-
result.put(key, getVal(itemMap.read(true).getValue()));
-
}
-
countDownLatch.countDown();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
上面的代码中我们用一个map将返回值给存了起来
再测一下!
结果:单线程情况下,请求1000条数据,耗时3秒左右
emmm....虽然进步了,但是1000条还是要3秒,2w条还是要1分钟左右,这能忍?不能!
于是我还想再找找有没有别的我没注意到的地方了,然后就发下了一行比较诡异的代码:
image.png
正常我们取值不是直接getValue()就好了?这个为什么还要read一下?
然后发现真正从客户端读取数据的就是这一行,我那个朋友之前误以为这个玩意儿就是取完值的集合了
image.png
但事实是,我们虽然分了组,但是取数据的时候还是一个item一个item地取,所以会如此之慢,,,,然后我又扒了一遍group的源码,又发现了这个东东:
image.png
看清没,我们之前是通过item去read值,但事实是,人家group本身就有一个read,而且将真正的值的集合返回给了我们,再稍微推敲一下,是不是这个组建好之后,我们可以取多次数据了。。。。
知道了这个方法,稍微改造了下代码,我批量穿参时候,也批量取,于是就有了如下代码!!!!(此处应有闪光特效)
-
package com.oukong.framework;
-
import org.jinterop.dcom.common.JIErrorCodes;
-
import org.jinterop.dcom.common.JIException;
-
import org.jinterop.dcom.core.JIVariant;
-
import org.openscada.opc.dcom.da.OPCSERVERSTATE;
-
import org.openscada.opc.lib.common.AlreadyConnectedException;
-
import org.openscada.opc.lib.common.ConnectionInformation;
-
import org.openscada.opc.lib.common.NotConnectedException;
-
import org.openscada.opc.lib.da.*;
-
import java.io.*;
-
import java.net.UnknownHostException;
-
import java.util.*;
-
import java.util.concurrent.CountDownLatch;
-
import java.util.concurrent.Executors;
-
public class OpcDaTest2 {
-
public static void test(List<String> itemList) {
-
List<String> itemList1 = itemList.subList(0, 500);
-
List<String> itemList2 = itemList.subList(1000, 2000);
-
final ConnectionInformation ci = new ConnectionInformation();
-
ci.setHost("10.10.1.13"); // KEPServer服务器所在IP
-
ci.setDomain(""); // 域 为空
-
ci.setUser("OPCuser");
-
ci.setPassword("Sa2022");
-
ci.setClsid("4B12BF21-3C60-4C48-A47F-E5F1E3BCFD34"); // OPCServer的注册表ID,可以在“组件服务”中查到
-
Item item = null;
-
Server server = new Server(ci, null);
-
Map<String, Object> result = new HashMap<>();
-
try {
-
server.connect();
-
long start = System.currentTimeMillis();
-
CountDownLatch countDownLatch = new CountDownLatch(1);
-
OpcThread thread = new OpcThread(server, itemList1, countDownLatch, result);
-
thread.start();
-
countDownLatch.await();
-
long end = System.currentTimeMillis();
-
System.out.println("totalSize: " + result.size() + "\tuse :" + (end - start) + "ms");
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
-
class OpcThread extends Thread {
-
private List<String> itemList;
-
private Server server;
-
private CountDownLatch countDownLatch;
-
private Map<String, Object> result;
-
public OpcThread(Server server, List<String> itemList, CountDownLatch countDownLatch,
-
Map<String, Object> result) {
-
this.server = server;
-
this.itemList = itemList;
-
this.countDownLatch = countDownLatch;
-
this.result = result;
-
}
-
@Override
-
public void run() {
-
Group group = null;
-
try {
-
// 建组
-
long s = System.currentTimeMillis();
-
group = server.addGroup();
-
String[] items = itemList.toArray(new String[]{});
-
Map<String, Item> itemResult = group.addItems(items);
-
System.out.println(itemResult.size());
-
long e = System.currentTimeMillis();
-
System.out.println("建组耗时:" + (e - s));
-
//第一次取数据
-
long start = System.currentTimeMillis();
-
Set itemSet = new HashSet(itemResult.values());
-
Item[] itemArr = new Item[itemSet.size()];
-
itemSet.toArray(itemArr);
-
Map<Item, ItemState> resultMap = group.read(true, itemArr);
-
for(Item key : resultMap.keySet()) {
-
ItemState itemMap = resultMap.get(key);
-
result.put(key.getId(), getVal(itemMap.getValue()));
-
}
-
long end = System.currentTimeMillis();
-
System.out.println("group1 totalSize1 : " + itemResult.size() + "\tuse :" + (end - start) + "ms");
-
//第二次取数据
-
long start2 = System.currentTimeMillis();
-
Map<Item, ItemState> resultMap2 = group.read(true, itemArr);
-
for(Item key : resultMap2.keySet()) {
-
ItemState itemMap = resultMap2.get(key);
-
result.put(key.getId(), getVal(itemMap.getValue()));
-
}
-
long end2 = System.currentTimeMillis();
-
System.out.println("group1 totalSize2 : " + resultMap2.size() + "\tuse :" + (end2 - start2) + "ms");
-
countDownLatch.countDown();
-
} catch (Exception e) {
-
e.printStackTrace();
-
}
-
}
-
}
这下我们彻底将组利用了起来,执行结果如下:
-
建组耗时:177214
-
group1 totalSize1 : 24000 use :6467ms
-
group1 totalSize2 : 24000 use :1526ms
我们将最初3分钟的时间,优化到了6秒钟,就问6不6?
最后附上完整的依赖和代码
maven
-
<!--utgard -->
-
<dependency>
-
<groupId>org.openscada.utgard</groupId>
-
<artifactId>org.openscada.opc.lib</artifactId>
-
<version>1.5.0</version>
-
<exclusions>
-
<exclusion>
-
<groupId>org.bouncycastle</groupId>
-
<artifactId>bcprov-jdk15on</artifactId>
-
</exclusion>
-
</exclusions>
-
</dependency>
-
<dependency>
-
<groupId>org.bouncycastle</groupId>
-
<artifactId>bcprov-jdk15on</artifactId>
-
<version>1.65</version>
-
</dependency>
-
<dependency>
-
<groupId>org.openscada.utgard</groupId>
-
<artifactId>org.openscada.opc.dcom</artifactId>
-
<version>1.5.0</version>
-
</dependency>
OPC客户端
-
import lombok.extern.slf4j.Slf4j;
-
import org.jinterop.dcom.common.JIErrorCodes;
-
import org.jinterop.dcom.common.JIException;
-
import org.jinterop.dcom.core.JIVariant;
-
import org.openscada.opc.dcom.da.OPCSERVERSTATE;
-
import org.openscada.opc.lib.common.AlreadyConnectedException;
-
import org.openscada.opc.lib.common.ConnectionInformation;
-
import org.openscada.opc.lib.common.NotConnectedException;
-
import org.openscada.opc.lib.da.*;
-
import java.net.UnknownHostException;
-
import java.util.*;
-
/**
-
* @Auther: 夏
-
* @DATE: 2022/6/8 14:58
-
* @Description: opc da客户端
-
*/
-
@Slf4j
-
public class OpcDAClient {
-
private String host;
-
private String user;
-
private String password;
-
private String clsId;
-
private Server server;
-
private String bakHost;
-
private Integer groupCount;
-
private Group group = null;
-
private Map<String, Item> groupItems = null;
-
/**
-
* 初始化连接信息
-
* @param host
-
* @param user
-
* @param password
-
* @param clsId
-
*/
-
public OpcDAClient(String host, String user, String password, String clsId, Integer groupCount) {
-
this.host = host;
-
this.user = user;
-
this.password = password;
-
this.clsId = clsId;
-
this.groupCount = groupCount;
-
}
-
/**
-
* 设置备用服务地址
-
* @param bakHost
-
*/
-
public void setBakHost(String bakHost) {
-
this.bakHost = bakHost;
-
}
-
/**
-
* 创建连接
-
*/
-
public void connect() {
-
if(server.getServerState() != null && server.getServerState().getServerState().equals(OPCSERVERSTATE.OPC_STATUS_RUNNING)) {
-
return;
-
}
-
final ConnectionInformation ci = new ConnectionInformation();
-
ci.setHost(host);
-
ci.setDomain(""); // 域 为空
-
ci.setUser(user);
-
ci.setPassword(password);
-
ci.setClsid(clsId);
-
server = new Server(ci, null);
-
try {
-
server.connect();
-
} catch (UnknownHostException e) {
-
e.printStackTrace();
-
log.error("opc 地址错误:", e);
-
} catch (JIException e) {
-
e.printStackTrace();
-
log.error("opc 连接失败:", e);
-
log.info("开始连接备用服务...");
-
try {
-
ci.setHost(bakHost);
-
server = new Server(ci, null);
-
server.connect();
-
} catch (Exception e2) {
-
log.error("备用服务连接失败:", e2);
-
}
-
} catch (AlreadyConnectedException e) {
-
e.printStackTrace();
-
log.error("opc 已连接:", e);
-
}
-
log.info("OPC Server connect success...");
-
}
-
/**
-
* 根据地址获取数据
-
* @param itemId
-
* @return
-
*/
-
public Object getItemValue(String itemId) {
-
try {
-
Group group = server.addGroup();
-
Item item = group.addItem(itemId);
-
return getVal(item.read(true).getValue());
-
} catch (Exception e) {
-
e.printStackTrace();
-
log.error("获取数据异常 itemId:{}", itemId, e);
-
}
-
return null;
-
}
-
/**
-
* 获取多组数据
-
* @param itemIdList
-
* @return
-
*/
-
public Map<String, Object> getItemValues(List<String> itemIdList) {
-
Map<String, Object> result = new HashMap<>();
-
try {
-
if(groupItems == null || group == null) {
-
log.info("开始建组...");
-
group = server.addGroup();
-
String[] items = itemIdList.toArray(new String[]{});
-
groupItems = group.addItems(items);
-
log.info("组建完成,开始查询数据...");
-
}
-
Set itemSet = new HashSet(groupItems.values());
-
Item[] itemArr = new Item[itemSet.size()];
-
itemSet.toArray(itemArr);
-
Map<Item, ItemState> resultMap = group.read(true, itemArr);
-
log.info("数据获取完成:{}条", resultMap.size());
-
for(Item item : resultMap.keySet()) {
-
ItemState itemMap = resultMap.get(item);
-
result.put(item.getId(), getVal(itemMap.getValue()));
-
}
-
} catch (Exception e) {
-
e.printStackTrace();
-
log.error("批量获取数据异常:", e);
-
}
-
return result;
-
}
-
/**
-
* 获取value
-
* @param var
-
* @return
-
* @throws JIException
-
*/
-
private static Object getVal(JIVariant var) throws JIException {
-
Object value;
-
int type = var.getType();
-
switch (type) {
-
case JIVariant.VT_I2:
-
value = var.getObjectAsShort();
-
break;
-
case JIVariant.VT_I4:
-
value = var.getObjectAsInt();
-
break;
-
case JIVariant.VT_I8:
-
value = var.getObjectAsLong();
-
break;
-
case JIVariant.VT_R4:
-
value = var.getObjectAsFloat();
-
break;
-
case JIVariant.VT_R8:
-
value = var.getObjectAsDouble();
-
break;
-
case JIVariant.VT_BSTR:
-
value = var.getObjectAsString2();
-
break;
-
case JIVariant.VT_BOOL:
-
value = var.getObjectAsBoolean();
-
break;
-
case JIVariant.VT_UI2:
-
case JIVariant.VT_UI4:
-
value = var.getObjectAsUnsigned().getValue();
-
break;
-
case JIVariant.VT_EMPTY:
-
throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is Empty.");
-
case JIVariant.VT_NULL:
-
throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Variant is null.");
-
default:
-
throw new JIException(JIErrorCodes.JI_VARIANT_IS_NULL, "Unknown Type.");
-
}
-
return value;
-
}
-
/**
-
* 将一组数据平均分成n组
-
*
-
* @param source 要分组的数据源
-
* @param n 平均分成n组
-
* @param <T>
-
* @return
-
*/
-
private static <T> List<List<T>> averageAssign(List<T> source, int n) {
-
List<List<T>> result = new ArrayList<List<T>>();
-
int remainder = source.size() % n; //(先计算出余数)
-
int number = source.size() / n; //然后是商
-
int offset = 0;//偏移量
-
for (int i = 0; i < n; i++) {
-
List<T> value = null;
-
if (remainder > 0) {
-
value = source.subList(i * number + offset, (i + 1) * number + offset + 1);
-
remainder --;
-
offset++;
-
} else {
-
value = source.subList(i * number + offset, (i + 1) * number + offset);
-
}
-
result.add(value);
-
}
-
return result;
-
}
-
/**
-
* 关闭连接
-
*/
-
public void disconnect() {
-
server.disconnect();
-
if (null == server.getServerState()) {
-
log.info("OPC Server Disconnect...");
-
}
-
}
-
}
最后,如果连接服务端有报错,可以到一下连接查看配置步骤和相关的错误编码