在使用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..."); -
} -
} -
}
最后,如果连接服务端有报错,可以到一下连接查看配置步骤和相关的错误编码
6252

被折叠的 条评论
为什么被折叠?



