Java实现OPC通信,java开发面试笔试题


我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家。
扫描二维码或搜索下图红色VX号,加VX好友,拉你进【程序员面试学习交流群】免费领取。也欢迎各位一起在群里探讨技术。
推荐文章:Java 面试知识点解析Mysql优化技巧(数据库设计、命名规范、索引优化

1.PLC和OPC

 

使用的PLC:西门子的S7 300,具体型号如下图


 

 

使用的OPC server软件:


 



  •  
  • 模拟仿真用的 MatrikonOPCSimulation(50M),百度网盘,密码: mcur


     
  • 项目使用KEPServer V6(450M,中文):百度网盘 ,密码: ykj2


     

2.连接测试

OPC server软件使用

Server和Client

 

要实现的是Client(Java)和Client(PLC)之间的通信

 

中间借助OPCServer,Server上设定好地址变量,不同的Client读写这些变量值实现通信。

 

示意图如下


 

配置Server和Client

 

OPC和DCOM配置

 

配置OPCserver


一般一个电脑(win10)同时安装Server(比如KEPServer)和Client(Java编写的),就配置这个电脑就行


如果是在两个电脑上,那就都需要配置。


 

3.通信实现

 

最重要参考:Java OPC client开发踩坑记


 

Utgard

 
 

博客参考

 
 

github上的

 
 

JeasyOPC

 
 

4.实现过程

1.补充学习了一下OPC的概念:

 
 

2.使用MatrikonOPC,了解OPCserver是怎么用的

 
 

3.关于OPC UA

 



  •  
  • 支持的OPC UA的西门子PLC至少是s7-1500


     
  • 我的s7-300是没法用的,所以就不需要搜集OPC UA的资料了


     

 

4.关于用Java实现

 



  •  
  • C#和C++都不用配置,直接调用函数


     
  • 既然是非要用Java,那就别想太方便。


     

 

5.关于Utgard

 



  •  
  • utgard是一个开源的项目,基于j-interop做的,用于和OPC SERVER通讯。


     
  • j-interop是纯java封装的用于COM/DCOM通讯的开源项目,这样就不必使用JNI


     

 

6.关于JeasyOPC

 



  •  
  • JeasyOPC源码下载


     
  • 借助一个dll库来实现的和OPCServer的通信,但是JCustomOpc.dll,,太老了,而且支持只32位系统


     

 

7.最终实现

 



  •  
  • 当然选Utgard


     
  • 过程就是把需要的jar包找到,


     
  • 然后复制编程指导里的读写代码,读就是启动线程一直监控读值,写就是直接写


     

 

8.测试

 



  •  
  • 参考OPC_Client里的例子


     
  • 关于配置文件的代码直接复制用了


     
  • 例子实际也用不到,试了试,,因为实际只需要读写变量就可以了


     

 

9.问题:

 



  •  
  • 在虚拟机里用localhost一直报错,要写固定IP才行


     
  • 需要下载一个bcprov-jdk16-146.jar包,因为报安全算法错误


     
 

位置:C:\Program Files\Java\jdk1.8.0_92\jre\lib\ext\bcprov-jdk16-146.jar


java.security位置:C:\Program Files\Java\jdk1.8.0_92\jre\lib\security\java.security


java.security最后添加一句:security.provider.x=org.bouncycastle.jce.provider.BouncyCastleProvider


 

10.maven依赖

        <!--utgard -->

        <dependency>

            <groupId>org.openscada.external</groupId>

            <artifactId>org.openscada.external.jcifs</artifactId>

            <version>1.2.25</version>

        </dependency>

        <dependency>

            <groupId>org.openscada.jinterop</groupId>

            <artifactId>org.openscada.jinterop.core</artifactId>

            <version>2.1.8</version>

        </dependency>

        <dependency>

            <groupId>org.openscada.jinterop</groupId>

            <artifactId>org.openscada.jinterop.deps</artifactId>

            <version>1.5.0</version>

        </dependency>

        <dependency>

            <groupId>org.openscada.utgard</groupId>

            <artifactId>org.openscada.opc.dcom</artifactId>

            <version>1.5.0</version>

        </dependency>

        <dependency>

            <groupId>org.openscada.utgard</groupId>

            <artifactId>org.openscada.opc.lib</artifactId>

            <version>1.5.0</version>

        </dependency>

        <dependency>

            <groupId>org.bouncycastle</groupId>

            <artifactId>bcprov-jdk15on</artifactId>

            <version>1.61</version>

        </dependency>

        <dependency>

            <groupId>ch.qos.logback</groupId>

            <artifactId>logback-core</artifactId>

            <version>1.3.0-alpha4</version>

        </dependency>

        <dependency>

            <groupId>ch.qos.logback</groupId>

            <artifactId>logback-classic</artifactId>

            <version>1.3.0-alpha4</version>

            <scope>test</scope>

        </dependency>

5.代码

下载:链接: 百度网盘 ,密码: x7f1

截图:

需求

读一次还是循环读,写一次还是一直写

读值或者写值的类型如果是数组怎么办?

根据实际使用,对例子加了注释,方便理解

读值


import java.util.concurrent.Executors;

 

import org.jinterop.dcom.common.JIException;

import org.openscada.opc.lib.common.ConnectionInformation;

import org.openscada.opc.lib.da.AccessBase;

import org.openscada.opc.lib.da.DataCallback;

import org.openscada.opc.lib.da.Item;

import org.openscada.opc.lib.da.ItemState;

import org.openscada.opc.lib.da.Server;

import org.openscada.opc.lib.da.SyncAccess;

 

public class UtgardTutorial1 {

 

    public static void main(String[] args) throws Exception {

        // 连接信息

        final ConnectionInformation ci = new ConnectionInformation(); 

        ci.setHost("192.168.0.1");         // 电脑IP

        ci.setDomain("");                  // 域,为空就行

        ci.setUser("OPCUser");             // 电脑上自己建好的用户名

        ci.setPassword("123456");          // 用户名的密码

 

        // 使用MatrikonOPC Server的配置

        // ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

        // final String itemId = "u.u";    //项的名字按实际

 

        // 使用KEPServer的配置

        ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // Clsid,软件在注册表的ID

        final String itemId = "u.u.u";    // 项的名字按实际

        // final String itemId = "通道 1.设备 1.标记 1";

 

        // 启动服务

        final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

 

        try {

            // 连接到服务

            server.connect();

            // add sync access, poll every 500 ms

            // 启动一个同步的access用来读取地址上的值,线程池每500ms读值一次

            // 这个是用来循环读值的,只读一次值不用这样

            final AccessBase access = new SyncAccess(server, 500);

            // 这是个回调函数,就是读到值后执行这个打印,是用匿名类写的,当然也可以写到外面去

            access.addItem(itemId, new DataCallback() {

                @Override

                public void changed(Item item, ItemState state) {

                    int type = itemState.getValue().getType();

                    System.out.println("监控项的数据类型是:-----" + type); //类型是个数字,用常量定义的

                    System.out.println("监控项的详细信息是:-----" + state);

 

                    // 如果读到是short类型的值

                    if (type == JIVariant.VT_I2) {

                        short n = state.getValue().getObjectAsShort();

                        System.out.println("-----short类型值: " + n); 

                    }

 

                    // 如果读到是字符串类型的值

                    if(type == JIVariant.VT_BSTR) {  // 字符串的类型是8

                        JIString value = itemState.getValue().getObjectAsString(); // 按字符串读取

                        String str = value.getString(); // 得到字符串

                        System.out.println("-----String类型值: " + str); 

                    }

                }

            });

            // start reading,开始读值

            access.bind();

            // wait a little bit,有个10秒延时

            Thread.sleep(10 * 1000);

            // stop reading,停止读取

            access.unbind();

        } catch (final JIException e) {

            System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));

        }

    }

}

写值


import java.util.concurrent.Executors;

import java.util.concurrent.ScheduledExecutorService;

import java.util.concurrent.TimeUnit;

 

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.AccessBase;

import org.openscada.opc.lib.da.DataCallback;

import org.openscada.opc.lib.da.Group;

import org.openscada.opc.lib.da.Item;

import org.openscada.opc.lib.da.ItemState;

import org.openscada.opc.lib.da.Server;

import org.openscada.opc.lib.da.SyncAccess;

 

public class UtgardTutorial2 {

    

    public static void main(String[] args) throws Exception {

 

        // 连接信息 

        final ConnectionInformation ci = new ConnectionInformation();

        

        ci.setHost("192.168.0.1");          // 电脑IP

        ci.setDomain("");                   // 域,为空就行

        ci.setUser("OPCUser");              // 用户名,配置DCOM时配置的

        ci.setPassword("123456");           // 密码

        

        // 使用MatrikonOPC Server时的配置

        // ci.setClsid("F8582CF2-88FB-11D0-B850-00C0F0104305");

        // final String itemId = "u.u";     //项的名字按实际

 

        // 使用KEPServer时的配置

        ci.setClsid("7BC0CC8E-482C-47CA-ABDC-0FE7F9C6E729"); // Clsid,软件在注册表的ID

        final String itemId = "u.u.u";      // 项的名字按实际

        // final String itemId = "通道 1.设备 1.标记 1";

        

        // create a new server,启动服务

        final Server server = new Server(ci, Executors.newSingleThreadScheduledExecutor());

        try {

            // connect to server,连接到服务

            server.connect();

 

            // 连接成功后就可以在别的地方使用了

            // 使用方法就是前面声明变量:private Server server;

            // 然后连接成功后给变量变量赋值:this.server = server;

 

            // add sync access, poll every 500 ms

            // 启动一个同步的access用来读取地址上的值,线程池每500ms读值一次

            // 这个是用来循环读值的,只读一次值不用这样

            final AccessBase access = new SyncAccess(server, 500);

            // 这是个回调函数,就是读到值后执行再执行下面的代码,是用匿名类写的,当然也可以写到外面去

            access.addItem(itemId, new DataCallback() {

                @Override

                public void changed(Item item, ItemState state) {

                    // also dump value

                    try {

                        if (state.getValue().getType() == JIVariant.VT_UI4) { // 如果读到的值类型时UnsignedInteger,即无符号整形数值

                            System.out.println("<<< " + state + " / value = " + state.getValue().getObjectAsUnsigned().getValue());

                        } else {

                            System.out.println("<<< " + state + " / value = " + state.getValue().getObject());

                        }

                    } catch (JIException e) {

                        e.printStackTrace();

                    }

 

                    // 如果读到的值是数组类型呢,下面为读取Float类型的数组

                    if (type == 8196) { // 8196是打印state.getValue().getType()得到的

                        JIArray jarr = state.getValue().getObjectAsArray(); // 按数组读取

                        Float[] arr = (Float[]) jarr.getArrayInstance();  // 得到数组

                        String value = "";

                        for (Float f : arr) {

                            value = value + f + ",";

                        }

                        System.out.println(value.substring(0, value.length() - 1); // 遍历打印数组的值,中间用逗号分隔,去掉最后都好

                    }

                }

            });

 

            // Add a new group,添加一个组,这个用来就读值或者写值一次,而不是循环

            // 组的名字随意,给组起名字是因为,server可以addGroup也可以removeGroup,读一次值,就先添加组,然后移除组,再读一次就再添加然后删除

            final Group group = server.addGroup("test"); 

            // Add a new item to the group,

            // 将一个item加入到组,item名字就是MatrikonOPC Server或者KEPServer上面建的项的名字比如:u.u.TAG1,PLC.S7-300.TAG1

            final Item item = group.addItem(itemId);

 

            // start reading,开始循环读值

            access.bind();

 

            // add a thread for writing a value every 3 seconds

            // 写一次就是item.write(value),一直写就起个线程一直执行item.write(value)

            ScheduledExecutorService writeThread = Executors.newSingleThreadScheduledExecutor();

            writeThread.scheduleWithFixedDelay(new Runnable() {

                @Override

                public void run() {

                    // 单个值,如1,"R37",就当字符串直接写入

                    final JIVariant value = new JIVariant("24");  // 写入24

                    try {

                        System.out.println(">>> " + "写入值:  " + "24");

                        item.write(value);

                    } catch (JIException e) {

                        e.printStackTrace();

                    }

 

                    // 如果写的值是数组类型呢,比如6位Long类型的数组

                    // 构造数组,前缀加(long)是因为编辑器对于数值一般按int类型处理,其他类型要指定类型

                    // Long[] integerData = {(long) 1,(long) 2,(long) 3,(long) 4,(long) 5,(long) 6}; 

                    // final JIArray array = new JIArray(integerData, false);  // 这句是抄来的

                    // final JIVariant value = new JIVariant(array);

                    // item.write(value);

                }

            }, 5, 3, TimeUnit.SECONDS); // 启动后5秒第一次执行代码,以后每3秒执行一次代码

 

            // wait a little bit ,延时20秒

            Thread.sleep(20 * 1000);

            writeThread.shutdownNow();  // 杀死一直读的线程

            // stop reading,停止循环读值

            access.unbind();

        } catch (final JIException e) {

            System.out.println(String.format("%08X: %s", e.getErrorCode(), server.getErrorMessage(e.getErrorCode())));

        }

    }

}


转载:https://www.cnblogs.com/ioufev/p/9928971.html

推荐内容:
一个两年Java的面试总结
阿里java面试题
java常见面试题
互联网高级Java面试总结
2年Java开发工作经验面试总结
【Java】广州三本秋招经历
十大经典排序算法最强总结(含JAVA代码实现)
Java 面试知识点解析(七)——Web篇
Java高级篇(一)——线程
Java 面试题 四

猜你喜欢

转载自blog.csdn.net/kuangdashi/article/details/89609507