JAVA接入OPC DA2.0引发的问题

超短连接转换j1z.cc(永久有效)

背景:

JAVA接入OPC DA后,在生产环境跑了一段时间后就会出现异常,给折腾的够呛,起初的报错还能通过重启OPC连接解决,后来强制重新连接也不行,最终一套测试下来,除非重启OPC服务器,别无他法!简单说下几个错误及导致的原因,以及给出解决方案!

错误一:An internal error occurred. [0x8001FFFF]

查询了很多资料无果,只有一些有年代的提问:An internal error occurred. [0x8001FFFF]
后来发现可以通过重启解决,于是有了直接判断异常,相同则重启OPC,解决燃眉之急。

        if ("An internal error occurred. [0x8001FFFF]".equals(e.getMessage())) {
            OpcUtils.forceReconnect();
        }

但目前并未查到原因!

错误二:org.jinterop.dcom.common.JIException: Message not found for errorCode: 0x800700A4

后来跑了一段时间后发现一直报异常0x800700A4,而且重连再也无法建立链接,但是可以打印OPC服务器的服务,只是无法建立连接!此时的我已经要炸了!由于测试环境无法达成与生产环境完全一致,无法进行测试,只能搜索问题,更头大的是使用OPC客户端软件也无法建立连接,此刻已经无解了。

查了很多,有的说是COM无法创建更多线程,也有说是windows系统bug,内存泄露需要更新补丁!但是依旧无有效解决方案。由于客户端也连接不上只能认为是OPC服务软件的问题了,每次重启都会恢复,运行一段时间就不行,但是OPC服务本身无任何问题。于是猜想DCOM有问题,我又写了一个基于COM的中转程序,想着本地连接肯定没问题了吧,很自信!

结果开心的时刻来了,跑去生产环境去部署发现COM方式也连接不上,脸打的生疼!最终还是完整部署了一份和生产环境一致的测试环境,经过我不懈的压测,最终OPC服务软件报了个错!(擦 生产环境为何没有),就因为这个错误提示,才引起我对一段代码的怀疑,之前觉得有疑惑但是没管它。

JAVA通过COM方式(jeasyopc)接入OPC DA

定位到问题

仅仅一个服务端OPC软件的异常 “AddGroup”,让我想起一段代码:

        //TODO 同步读取数据
        try {
            Group group = server.addGroup();
            Map<String, Item> itemMap = group.addItems(itemIds.toArray(new String[0]));
            List<DataItem> result = new ArrayList<>();
            for (Map.Entry<String, Item> entry : itemMap.entrySet()) {
                Item item = entry.getValue();
                ItemState itemState = item.read(true);
                DataItem dataItem = JiVariantUtil.parseValue(item.getId(), itemState);
                result.add(dataItem);
            }
//            group.clear();
//            server.removeGroup(group,false);
            return result;
        } catch (Exception e) {
            log.error("同步读取失败!", e);
            return null;
        }

这段查询代码来源于网络,当时很纳闷为何注释了两行,因为也浅显的了解了下OPC知识,知道关于group分组是在客户端定义,当时脑里的思路还在想正常定义分组应该初始化定义好同步给OPC服务器,后面直接拿分组名称用就是了,这里等于每次都会新增分组,注释的是清除,现在想想也真是大意了,狠狠的吃了把偷懒的亏!

结论显而易见,由于不断的addGroup,导致OPC服务器崩溃了。

优化代码

方法一

最简单的优化方式,每次新增,并且每次移除,只需要把注释的代码修改为:

server.removeGroup(group,true);

本人已压测过千万次查询写入,没有问题,效率会低点。

方法二

当前最优解应该是项目启动的时候初始化分组,addGroup后记录分组的名称,并在后续的交互中使用Group名称进行交互!写的过程中才发现,原来自带了很多方法就是为了这么用的,没文档,只能看代码了。大致列下实现:

   public static List<Item> getOrCreateOpcItem(Collection<String> itemIds) {
      Server server = getServer();
       if (Objects.isNull(server)) {
           log.error("OPC 获取OPC item时,服务为空!获取ItemIds:{}", itemIds);
           return null;
       }
       String key = String.join(",", itemIds);
       List<Item> result = new ArrayList<>();
       try {
           try {
               Group group = server.findGroup(key);
               List<Integer> integers = OpcUtils.itemClientHandleMap.get(key);
               for (Integer integer : integers) {
                   result.add(group.findItemByClientHandle(integer));
               }
           } catch (UnknownGroupException e) {
               Group group = server.addGroup(key);
               Map<String, Item> itemMap = group.addItems(itemIds.toArray(new String[0]));
               result = new ArrayList<>(itemMap.values());
               OpcUtils.itemClientHandleMap.put(key, result.stream().map(Item::getClientHandle).collect(Collectors.toList()));
           }
       } catch (Exception e) {
           log.error("OPC 查询/创建OPC Group及Item异常,ItemIds:{}", itemIds, e);
           return null;
       }
       return result;
   }
   
  public static void writeSync(String itemId, Boolean value) {
      try {
           List<Item> orCreateOpcItems = getOrCreateOpcItem(List.of(itemId));
           if (Objects.isNull(orCreateOpcItems)) {
               log.error("OPC-Sync write failed! 获取item失败,ItemId: {}, Value: {}", itemId, value);
               return;
           }
           Item item = orCreateOpcItems.get(0);
           JIVariant jiVariant = new JIVariant(value);
           item.write(jiVariant);
       } catch (Exception e) {
           log.error("OPC-Sync write failed! ItemId: {}, Value: {}", itemId, value, e);
       }
   }
    
   public static List<DataItem> readSync(Collection<String> itemIds) {
       try {
           List<Item> orCreateOpcItems = getOrCreateOpcItem(itemIds);
           if (Objects.isNull(orCreateOpcItems)) {
               log.error("OPC-Sync read failed! 获取item失败,ItemId: {}", itemIds);
               return null;
           }
           List<DataItem> result = new ArrayList<>();
           for (Item item : orCreateOpcItems) {
               ItemState itemState = item.read(true);
               DataItem dataItem = JiVariantUtil.parseValue(item.getId(), itemState);
               result.add(dataItem);
           }
           return result;
       } catch (Exception e) {
           log.error("同步读取失败!itemIds:[{}]", itemIds, e);
           return null;
       }
   }

OPC DA客户端工具(从KEPServerEX 6精简):

链接: https://pan.baidu.com/s/1kAZJHdIDxOxjIXhdfnLJ_Q?pwd=his3

提取码: his3

总结

  1. 测试环境一定要与生产环境完全一致!
  2. 一定测试!长时间压测!
  3. 补足基础知识,查看源码,正确使用方式其义自见

参考:

microsoft security essentials errorcode 0x800700a4
.Net: I got the following exception InteropServices.COMException: "No more threads can be created in the system.

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值