参考
1、添加依赖
<!--客户端开发包--><dependency><groupId>org.eclipse.milo</groupId><artifactId>sdk-client</artifactId><version>0.4.0</version></dependency><!--服务端开发包--><dependency><groupId>org.eclipse.milo</groupId><artifactId>sdk-server</artifactId><version>0.4.0</version></dependency><!--证书解析相关--><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15to18</artifactId><version>1.66</version></dependency>
2、创建证书类
因为opcua client 和服务器连接通信 必须需要证书,所以提供一个创建数字证书的类
public class KeyStoreLoader {private final Logger logger = LoggerFactory.getLogger(getClass());private static final Pattern IP_ADDR_PATTERN = Pattern.compile("^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");// 证书别名private static final String CLIENT_ALIAS = "opcua-client";// 获取私钥的密码private static final char[] PASSWORD = "password".toCharArray();// 证书对象private X509Certificate clientCertificate;// 密钥对对象private KeyPair clientKeyPair;/*** @Author: 李孟帅* @CreateTime: 2021/6/2 15:04* @Description: 默认路径 存放证书的目录*/public Path defaultPath() throws Exception {Path crtDir = Paths.get(System.getProperty("user.home"), ".crt");Files.createDirectories(crtDir);if (!Files.exists(crtDir)) {throw new Exception("unable to create certificate dir: " + crtDir);}return crtDir;}public KeyStoreLoader load() throws Exception {return load(defaultPath());}public KeyStoreLoader load(Path baseDir) throws Exception {// 创建一个使用`PKCS12`加密标准的KeyStore。KeyStore在后面将作为读取和生成证书的对象。KeyStore keyStore = KeyStore.getInstance("PKCS12");// PKCS12的加密标准的文件后缀是.pfx,其中包含了公钥和私钥。// 而其他如.der等的格式只包含公钥,私钥在另外的文件中。Path clientKeyStore = baseDir.resolve("opcua-client.pfx");logger.info("Loading KeyStore at {}", clientKeyStore);// 如果文件不存在则创建.pfx证书文件。if (!Files.exists(clientKeyStore)) {keyStore.load(null, PASSWORD);// 用2048位的RAS算法。`SelfSignedCertificateGenerator`为Milo库的对象。KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);// `SelfSignedCertificateBuilder`也是Milo库的对象,用来生成证书。// 中间所设置的证书属性可以自行修改。SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair).setCommonName("Eclipse Milo Example Client").setOrganization("digitalpetri").setOrganizationalUnit("dev").setLocalityName("Folsom").setStateName("CA").setCountryCode("US").setApplicationUri("urn:eclipse:milo:examples:client").addDnsName("localhost").addIpAddress("127.0.0.1");// Get as many hostnames and IP addresses as we can listed in the certificate.for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {if (IP_ADDR_PATTERN.matcher(hostname).matches()) {builder.addIpAddress(hostname);} else {builder.addDnsName(hostname);}}// 创建证书X509Certificate certificate = builder.build();// 设置对应私钥的别名,密码,证书链keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});try (OutputStream out = Files.newOutputStream(clientKeyStore)) {// 保存证书到输出流keyStore.store(out, PASSWORD);}} else {try (InputStream in = Files.newInputStream(clientKeyStore)) {// 如果文件存在则读取keyStore.load(in, PASSWORD);}}// 用密码获取对应别名的私钥。Key clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);if (clientPrivateKey instanceof PrivateKey) {// 获取对应别名的证书对象。clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);// 获取公钥PublicKey serverPublicKey = clientCertificate.getPublicKey();// 创建Keypair对象。clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) clientPrivateKey);}return this;}public X509Certificate getClientCertificate() {return clientCertificate;}public KeyPair getClientKeyPair() {return clientKeyPair;}}
第一次因为没有证书和密钥,所以可能比较慢,第二次不需要创建了,直接获取,速度比较快
3、创建OpcUaClient
package com.lms.opcua.eclipse.milo;import org.eclipse.milo.opcua.sdk.client.OpcUaClient;import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;import org.eclipse.milo.opcua.sdk.client.api.nodes.Node;import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;import org.eclipse.milo.opcua.stack.client.security.ClientCertificateValidator;import org.eclipse.milo.opcua.stack.core.AttributeId;import org.eclipse.milo.opcua.stack.core.Identifiers;import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;import java.util.ArrayList;import java.util.List;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;/*** @Author: 李孟帅* @CreateTime: 2021/6/2$ 11:47$* @Description: TODO*/public class Test {static String kepServerEndpoint = "opc.tcp://127.0.0.1:49320";static String endpoint = "opc.tcp://127.0.0.1:53530/OPCUA/SimulationServer";private List<NodeId> nodeIds = new ArrayList<>();private OpcUaClient opcUaClient;{try {// 获取证书和密钥对类KeyStoreLoader loader = new KeyStoreLoader().load();// 匿名访问AnonymousProvider anonymousProvider = new AnonymousProvider();UsernameProvider usernameProvider = new UsernameProvider("lms", "lms123");// 创建客户端,配置参数opcUaClient = OpcUaClient.create(kepServerEndpoint, endpoints -> endpoints.stream().filter(e -> e.getSecurityMode() == MessageSecurityMode.None).findFirst(), configBuilder ->configBuilder.setApplicationName(LocalizedText.english("eclipse milo opc-ua client")) // 任意写.setApplicationUri("urn:eclipse:milo:examples:client") // 需要和生成证书时保持一致,不然报错.setCertificate(loader.getClientCertificate()).setKeyPair(loader.getClientKeyPair()).setIdentityProvider(usernameProvider).setRequestTimeout(uint(5000)).setKeepAliveFailuresAllowed(UInteger.MIN).setCertificateValidator(new ClientCertificateValidator.InsecureValidator()).build());// 连接,设置超时5秒opcUaClient.connect().get(5, TimeUnit.SECONDS);} catch (Exception e) {e.printStackTrace();}}public static void main(String[] args) throws Exception {// new Test().daTest();new Test().subscribeNode();Thread.sleep(1000000000);}public void daTest() throws Exception {// String nodeId = "ns=5;s=85/0:Simulation";// Node rootNode = opcUaClient.getAddressSpace().getNodeInstance(NodeId.parse(nodeId)).get();// assemble(rootNode);List<Node> nodes = opcUaClient.getAddressSpace().browse(Identifiers.RootFolder).get();for (Node node : nodes) {if (node.getBrowseName().get().getName().equals("Objects")) {assemble(node);}}readValues();System.out.println("NodeId 的个数:" + nodeIds.size());}private void assemble(Node node) {try {List<Node> nodes = opcUaClient.getAddressSpace().browseNode(node).get();for (Node node1 : nodes) {// 递归读取所有点叶子节点,Variable也可能有子节点if (node1.getNodeClass().get() == NodeClass.Variable) {nodeIds.add(node1.getNodeId().get());// System.out.println("NodeName = " + node1.getBrowseName().get().getName() + " NodeId = " + node1.getNodeId().get());}assemble(node1);}} catch (InterruptedException | ExecutionException e) {try {System.out.println("异常信息:" + "NodeName = " + node.getBrowseName().get().getName() + " NodeId = " + node.getNodeId().get());} catch (InterruptedException | ExecutionException ex) {ex.printStackTrace();}e.printStackTrace();}}public void readValues() {CompletableFuture<List<DataValue>> dataFuture = opcUaClient.readValues(0, TimestampsToReturn.Both, nodeIds);dataFuture.thenAccept(dataValues -> {dataValues.forEach(dataValue -> {Variant value = dataValue.getValue();System.out.println(value + " ===> " + dataValue.getValue().getValue());});});// 该线程是守护线程,没有前台线程时,会直接结束,所以需要让其执行完dataFuture.join();}public void subscribeNode() throws ExecutionException, InterruptedException {//创建发布间隔1000ms的订阅对象UaSubscription subscription = opcUaClient.getSubscriptionManager().createSubscription(1000.0).get();// 请求间隔,一秒请求一次,如果数据变化了,不请求也获取不到数据//创建订阅的变量NodeId nodeId = new NodeId(2, "channel1.device1.tag1");ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);//创建监控的参数MonitoringParameters parameters = new MonitoringParameters(uint(1),1000.0, // sampling interval,采样的周期,数据变化时 一秒采集一次,如果数据没有变化,不会采集null, // filter, null means use defaultuint(10), // queue sizetrue // discard oldest);//创建监控项请求//该请求最后用于创建订阅。MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);List<MonitoredItemCreateRequest> requests = new ArrayList<>();requests.add(request);//创建监控项,并且注册变量值改变时候的回调函数。List<UaMonitoredItem> items = subscription.createMonitoredItems(TimestampsToReturn.Both,requests,(item, id) -> {item.setValueConsumer((item1, value) -> {System.out.println("nodeId :" + item1.getReadValueId().getNodeId());System.out.println("value :" + value.getValue().getValue());});}).get();}}
需要注意的点:
1、安全策略一般默认为None,表示通信不签名不加密
2、安全策略如果为Sign或SignAndEncrypt表示通信需要签名或签名并加密 ,服务器需要将客户端的数字证书添加到信任表中,才可以连接成功,注意设置客户端参数时ApplicationUri需要和生成证书时一致
