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

附加错误编码详解,opc配置和解决方法

OPC服务端和客户端配置_xnian_的博客-CSDN博客1、如果不清楚用户组针对哪些服务权限,就不操作用户组。给所需要的服务权限控制上直接加用户就可以了。2、经测试,客户端的用户名和服务端的一样都是admin,但是密码不同,依然可以调用opc服务端。3、连接失败的原因,基本都由于服务端的配置(包括授权配置、网络配置),或者程序中参数写错(或找错)导致。https://blog.csdn.net/xnian_/article/details/130531521

 OPCServer相关下载与使用icon-default.png?t=N4N7https://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...");
        }
    }

}

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

OPC服务端和客户端配置_xnian_的博客-CSDN博客1、如果不清楚用户组针对哪些服务权限,就不操作用户组。给所需要的服务权限控制上直接加用户就可以了。2、经测试,客户端的用户名和服务端的一样都是admin,但是密码不同,依然可以调用opc服务端。3、连接失败的原因,基本都由于服务端的配置(包括授权配置、网络配置),或者程序中参数写错(或找错)导致。https://blog.csdn.net/xnian_/article/details/130531521

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值