参考

1、添加依赖

  1. <!--客户端开发包-->
  2. <dependency>
  3. <groupId>org.eclipse.milo</groupId>
  4. <artifactId>sdk-client</artifactId>
  5. <version>0.4.0</version>
  6. </dependency>
  7. <!--服务端开发包-->
  8. <dependency>
  9. <groupId>org.eclipse.milo</groupId>
  10. <artifactId>sdk-server</artifactId>
  11. <version>0.4.0</version>
  12. </dependency>
  13. <!--证书解析相关-->
  14. <dependency>
  15. <groupId>org.bouncycastle</groupId>
  16. <artifactId>bcpkix-jdk15to18</artifactId>
  17. <version>1.66</version>
  18. </dependency>

2、创建证书类

因为opcua client 和服务器连接通信 必须需要证书,所以提供一个创建数字证书的类

  1. public class KeyStoreLoader {
  2. private final Logger logger = LoggerFactory.getLogger(getClass());
  3. private static final Pattern IP_ADDR_PATTERN = Pattern.compile(
  4. "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}([01]?\\d\\d?|2[0-4]\\d|25[0-5])$");
  5. // 证书别名
  6. private static final String CLIENT_ALIAS = "opcua-client";
  7. // 获取私钥的密码
  8. private static final char[] PASSWORD = "password".toCharArray();
  9. // 证书对象
  10. private X509Certificate clientCertificate;
  11. // 密钥对对象
  12. private KeyPair clientKeyPair;
  13. /**
  14. * @Author: 李孟帅
  15. * @CreateTime: 2021/6/2 15:04
  16. * @Description: 默认路径 存放证书的目录
  17. */
  18. public Path defaultPath() throws Exception {
  19. Path crtDir = Paths.get(System.getProperty("user.home"), ".crt");
  20. Files.createDirectories(crtDir);
  21. if (!Files.exists(crtDir)) {
  22. throw new Exception("unable to create certificate dir: " + crtDir);
  23. }
  24. return crtDir;
  25. }
  26. public KeyStoreLoader load() throws Exception {
  27. return load(defaultPath());
  28. }
  29. public KeyStoreLoader load(Path baseDir) throws Exception {
  30. // 创建一个使用`PKCS12`加密标准的KeyStore。KeyStore在后面将作为读取和生成证书的对象。
  31. KeyStore keyStore = KeyStore.getInstance("PKCS12");
  32. // PKCS12的加密标准的文件后缀是.pfx,其中包含了公钥和私钥。
  33. // 而其他如.der等的格式只包含公钥,私钥在另外的文件中。
  34. Path clientKeyStore = baseDir.resolve("opcua-client.pfx");
  35. logger.info("Loading KeyStore at {}", clientKeyStore);
  36. // 如果文件不存在则创建.pfx证书文件。
  37. if (!Files.exists(clientKeyStore)) {
  38. keyStore.load(null, PASSWORD);
  39. // 用2048位的RAS算法。`SelfSignedCertificateGenerator`为Milo库的对象。
  40. KeyPair keyPair = SelfSignedCertificateGenerator.generateRsaKeyPair(2048);
  41. // `SelfSignedCertificateBuilder`也是Milo库的对象,用来生成证书。
  42. // 中间所设置的证书属性可以自行修改。
  43. SelfSignedCertificateBuilder builder = new SelfSignedCertificateBuilder(keyPair)
  44. .setCommonName("Eclipse Milo Example Client")
  45. .setOrganization("digitalpetri")
  46. .setOrganizationalUnit("dev")
  47. .setLocalityName("Folsom")
  48. .setStateName("CA")
  49. .setCountryCode("US")
  50. .setApplicationUri("urn:eclipse:milo:examples:client")
  51. .addDnsName("localhost")
  52. .addIpAddress("127.0.0.1");
  53. // Get as many hostnames and IP addresses as we can listed in the certificate.
  54. for (String hostname : HostnameUtil.getHostnames("0.0.0.0")) {
  55. if (IP_ADDR_PATTERN.matcher(hostname).matches()) {
  56. builder.addIpAddress(hostname);
  57. } else {
  58. builder.addDnsName(hostname);
  59. }
  60. }
  61. // 创建证书
  62. X509Certificate certificate = builder.build();
  63. // 设置对应私钥的别名,密码,证书链
  64. keyStore.setKeyEntry(CLIENT_ALIAS, keyPair.getPrivate(), PASSWORD, new X509Certificate[]{certificate});
  65. try (OutputStream out = Files.newOutputStream(clientKeyStore)) {
  66. // 保存证书到输出流
  67. keyStore.store(out, PASSWORD);
  68. }
  69. } else {
  70. try (InputStream in = Files.newInputStream(clientKeyStore)) {
  71. // 如果文件存在则读取
  72. keyStore.load(in, PASSWORD);
  73. }
  74. }
  75. // 用密码获取对应别名的私钥。
  76. Key clientPrivateKey = keyStore.getKey(CLIENT_ALIAS, PASSWORD);
  77. if (clientPrivateKey instanceof PrivateKey) {
  78. // 获取对应别名的证书对象。
  79. clientCertificate = (X509Certificate) keyStore.getCertificate(CLIENT_ALIAS);
  80. // 获取公钥
  81. PublicKey serverPublicKey = clientCertificate.getPublicKey();
  82. // 创建Keypair对象。
  83. clientKeyPair = new KeyPair(serverPublicKey, (PrivateKey) clientPrivateKey);
  84. }
  85. return this;
  86. }
  87. public X509Certificate getClientCertificate() {
  88. return clientCertificate;
  89. }
  90. public KeyPair getClientKeyPair() {
  91. return clientKeyPair;
  92. }
  93. }

第一次因为没有证书和密钥,所以可能比较慢,第二次不需要创建了,直接获取,速度比较快

3、创建OpcUaClient

  1. package com.lms.opcua.eclipse.milo;
  2. import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
  3. import org.eclipse.milo.opcua.sdk.client.api.identity.AnonymousProvider;
  4. import org.eclipse.milo.opcua.sdk.client.api.identity.UsernameProvider;
  5. import org.eclipse.milo.opcua.sdk.client.api.nodes.Node;
  6. import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
  7. import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
  8. import org.eclipse.milo.opcua.stack.client.security.ClientCertificateValidator;
  9. import org.eclipse.milo.opcua.stack.core.AttributeId;
  10. import org.eclipse.milo.opcua.stack.core.Identifiers;
  11. import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
  12. import org.eclipse.milo.opcua.stack.core.types.builtin.LocalizedText;
  13. import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
  14. import org.eclipse.milo.opcua.stack.core.types.builtin.Variant;
  15. import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
  16. import org.eclipse.milo.opcua.stack.core.types.enumerated.MessageSecurityMode;
  17. import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
  18. import org.eclipse.milo.opcua.stack.core.types.enumerated.NodeClass;
  19. import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
  20. import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
  21. import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
  22. import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;
  23. import java.util.ArrayList;
  24. import java.util.List;
  25. import java.util.concurrent.CompletableFuture;
  26. import java.util.concurrent.ExecutionException;
  27. import java.util.concurrent.TimeUnit;
  28. import static org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.Unsigned.uint;
  29. /**
  30. * @Author: 李孟帅
  31. * @CreateTime: 2021/6/2$ 11:47$
  32. * @Description: TODO
  33. */
  34. public class Test {
  35. static String kepServerEndpoint = "opc.tcp://127.0.0.1:49320";
  36. static String endpoint = "opc.tcp://127.0.0.1:53530/OPCUA/SimulationServer";
  37. private List<NodeId> nodeIds = new ArrayList<>();
  38. private OpcUaClient opcUaClient;
  39. {
  40. try {
  41. // 获取证书和密钥对类
  42. KeyStoreLoader loader = new KeyStoreLoader().load();
  43. // 匿名访问
  44. AnonymousProvider anonymousProvider = new AnonymousProvider();
  45. UsernameProvider usernameProvider = new UsernameProvider("lms", "lms123");
  46. // 创建客户端,配置参数
  47. opcUaClient = OpcUaClient.create(kepServerEndpoint
  48. , endpoints -> endpoints.stream()
  49. .filter(e -> e.getSecurityMode() == MessageSecurityMode.None)
  50. .findFirst()
  51. , configBuilder ->
  52. configBuilder
  53. .setApplicationName(LocalizedText.english("eclipse milo opc-ua client")) // 任意写
  54. .setApplicationUri("urn:eclipse:milo:examples:client") // 需要和生成证书时保持一致,不然报错
  55. .setCertificate(loader.getClientCertificate())
  56. .setKeyPair(loader.getClientKeyPair())
  57. .setIdentityProvider(usernameProvider)
  58. .setRequestTimeout(uint(5000))
  59. .setKeepAliveFailuresAllowed(UInteger.MIN)
  60. .setCertificateValidator(new ClientCertificateValidator.InsecureValidator())
  61. .build());
  62. // 连接,设置超时5秒
  63. opcUaClient.connect().get(5, TimeUnit.SECONDS);
  64. } catch (Exception e) {
  65. e.printStackTrace();
  66. }
  67. }
  68. public static void main(String[] args) throws Exception {
  69. // new Test().daTest();
  70. new Test().subscribeNode();
  71. Thread.sleep(1000000000);
  72. }
  73. public void daTest() throws Exception {
  74. // String nodeId = "ns=5;s=85/0:Simulation";
  75. // Node rootNode = opcUaClient.getAddressSpace().getNodeInstance(NodeId.parse(nodeId)).get();
  76. // assemble(rootNode);
  77. List<Node> nodes = opcUaClient.getAddressSpace().browse(Identifiers.RootFolder).get();
  78. for (Node node : nodes) {
  79. if (node.getBrowseName().get().getName().equals("Objects")) {
  80. assemble(node);
  81. }
  82. }
  83. readValues();
  84. System.out.println("NodeId 的个数:" + nodeIds.size());
  85. }
  86. private void assemble(Node node) {
  87. try {
  88. List<Node> nodes = opcUaClient.getAddressSpace().browseNode(node).get();
  89. for (Node node1 : nodes) {
  90. // 递归读取所有点叶子节点,Variable也可能有子节点
  91. if (node1.getNodeClass().get() == NodeClass.Variable) {
  92. nodeIds.add(node1.getNodeId().get());
  93. // System.out.println("NodeName = " + node1.getBrowseName().get().getName() + " NodeId = " + node1.getNodeId().get());
  94. }
  95. assemble(node1);
  96. }
  97. } catch (InterruptedException | ExecutionException e) {
  98. try {
  99. System.out.println("异常信息:" + "NodeName = " + node.getBrowseName().get().getName() + " NodeId = " + node.getNodeId().get());
  100. } catch (InterruptedException | ExecutionException ex) {
  101. ex.printStackTrace();
  102. }
  103. e.printStackTrace();
  104. }
  105. }
  106. public void readValues() {
  107. CompletableFuture<List<DataValue>> dataFuture = opcUaClient.readValues(0, TimestampsToReturn.Both, nodeIds);
  108. dataFuture.thenAccept(dataValues -> {
  109. dataValues.forEach(dataValue -> {
  110. Variant value = dataValue.getValue();
  111. System.out.println(value + " ===> " + dataValue.getValue().getValue());
  112. });
  113. });
  114. // 该线程是守护线程,没有前台线程时,会直接结束,所以需要让其执行完
  115. dataFuture.join();
  116. }
  117. public void subscribeNode() throws ExecutionException, InterruptedException {
  118. //创建发布间隔1000ms的订阅对象
  119. UaSubscription subscription = opcUaClient.getSubscriptionManager().createSubscription(1000.0).get();// 请求间隔,一秒请求一次,如果数据变化了,不请求也获取不到数据
  120. //创建订阅的变量
  121. NodeId nodeId = new NodeId(2, "channel1.device1.tag1");
  122. ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, null);
  123. //创建监控的参数
  124. MonitoringParameters parameters = new MonitoringParameters(
  125. uint(1),
  126. 1000.0, // sampling interval,采样的周期,数据变化时 一秒采集一次,如果数据没有变化,不会采集
  127. null, // filter, null means use default
  128. uint(10), // queue size
  129. true // discard oldest
  130. );
  131. //创建监控项请求
  132. //该请求最后用于创建订阅。
  133. MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
  134. List<MonitoredItemCreateRequest> requests = new ArrayList<>();
  135. requests.add(request);
  136. //创建监控项,并且注册变量值改变时候的回调函数。
  137. List<UaMonitoredItem> items = subscription.createMonitoredItems(
  138. TimestampsToReturn.Both,
  139. requests,
  140. (item, id) -> {
  141. item.setValueConsumer((item1, value) -> {
  142. System.out.println("nodeId :" + item1.getReadValueId().getNodeId());
  143. System.out.println("value :" + value.getValue().getValue());
  144. });
  145. }
  146. ).get();
  147. }
  148. }

需要注意的点:
1、安全策略一般默认为None,表示通信不签名不加密
2、安全策略如果为Sign或SignAndEncrypt表示通信需要签名或签名并加密 ,服务器需要将客户端的数字证书添加到信任表中,才可以连接成功,注意设置客户端参数时ApplicationUri需要和生成证书时一致