参考
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 default
uint(10), // queue size
true // 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需要和生成证书时一致