附加错误编码详解,opc配置和解决方法
OPCServer相关下载与使用https://www.cnblogs.com/ioufev/p/9366877.html
Long time no see!
在使用opc之前我们先了解一下什么是opc,首先OPC包含三个概念模型:
- OPC Server
-
OPC Group(注意这个加粗!!!)
- OPC Item
关于这三个概念模型具体含义,我就不一一赘述了,大家可点进下面的连接查看
http://www.laomaozy.com/W-Z/208219.html
首先,我们的需求是,用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...");
}
}
}
最后,如果连接服务端有报错,可以到一下连接查看配置步骤和相关的错误编码