diff --git a/taoyao-server/src/test/java/com/acgist/taoyao/signal/SignalTest.java b/taoyao-server/src/test/java/com/acgist/taoyao/signal/SignalTest.java new file mode 100644 index 0000000..4ed4042 --- /dev/null +++ b/taoyao-server/src/test/java/com/acgist/taoyao/signal/SignalTest.java @@ -0,0 +1,26 @@ +package com.acgist.taoyao.signal; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.net.http.WebSocket; + +import org.junit.jupiter.api.Test; + +import com.acgist.taoyao.main.TaoyaoApplication; +import com.acgist.taoyao.test.annotation.TaoyaoTest; + +@TaoyaoTest(classes = TaoyaoApplication.class) +class SignalTest { + + @Test + void testSignal() throws InterruptedException { + final WebSocket clientA = WebSocketClient.build("wss://localhost:8888/websocket.signal", "clientA"); + final WebSocket clientB = WebSocketClient.build("wss://localhost:8888/websocket.signal", "clientB"); + clientA.sendText(""" + {"header":{"pid":1000,"v":"1.0.0","id":"1","sn":"clientA"},"body":{}} + """, true).join(); + assertNotNull(clientA); + assertNotNull(clientB); + } + +} diff --git a/taoyao-server/src/test/java/com/acgist/taoyao/signal/WebSocketClient.java b/taoyao-server/src/test/java/com/acgist/taoyao/signal/WebSocketClient.java new file mode 100644 index 0000000..668405f --- /dev/null +++ b/taoyao-server/src/test/java/com/acgist/taoyao/signal/WebSocketClient.java @@ -0,0 +1,88 @@ +package com.acgist.taoyao.signal; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.WebSocket; +import java.net.http.WebSocket.Listener; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.util.concurrent.CompletionStage; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class WebSocketClient { + + public static final WebSocket build(String uri, String sn) throws InterruptedException { + final Object lock = new Object(); + try { + return HttpClient + .newBuilder() + .sslContext(newSSLContext()) + .build() + .newWebSocketBuilder() + .buildAsync(URI.create(uri), new Listener() { + @Override + public void onOpen(WebSocket webSocket) { + webSocket.sendText(String.format(""" + {"header":{"pid":2000,"v":"1.0.0","id":"1","sn":"%s"},"body":{"username":"taoyao","password":"taoyao"}} + """, sn), true); + Listener.super.onOpen(webSocket); + } + @Override + public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { + synchronized (lock) { + lock.notifyAll(); + } + log.info("收到WebSocket消息:{}", data); + return Listener.super.onText(webSocket, data, last); + } + }) + .join(); + } finally { + synchronized (lock) { + lock.wait(1000); + } + } + } + + private static final SSLContext newSSLContext() { + SSLContext sslContext = null; + try { + // SSL协议:SSL、SSLv2、SSLv3、TLS、TLSv1、TLSv1.1、TLSv1.2、TLSv1.3 + sslContext = SSLContext.getInstance("TLSv1.2"); + sslContext.init(null, TRUST_ALL_CERT_MANAGER, new SecureRandom()); + } catch (KeyManagementException | NoSuchAlgorithmException e) { + log.error("新建SSLContext异常", e); + try { + sslContext = SSLContext.getDefault(); + } catch (NoSuchAlgorithmException ex) { + log.error("新建默认SSLContext异常", ex); + } + } + return sslContext; + } + + private static final TrustManager[] TRUST_ALL_CERT_MANAGER = new TrustManager[] { + new X509TrustManager() { + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + @Override + public X509Certificate[] getAcceptedIssuers() { + return null; + } + } + }; + +} diff --git a/taoyao-signal/README.md b/taoyao-signal/README.md index 5f75b05..2c381c3 100644 --- a/taoyao-signal/README.md +++ b/taoyao-signal/README.md @@ -41,7 +41,7 @@ #### 消息流程:终端->服务端+)终端 -全员广播关闭服务信令,然后关闭信令服务。 +全员广播[关闭服务信令](#关闭服务信令1000),然后关闭信令服务。 ### 执行命令信令(1001) @@ -60,7 +60,7 @@ #### 消息流程:终端->服务端->终端 -在服务端执行终端命令并返回结果 +执行命令同时响应结果 ### 异常信令(1999) diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java index fa58efe..b54c101 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/ProtocolManager.java @@ -67,22 +67,27 @@ public class ProtocolManager { */ public void execute(String message, AutoCloseable instance) { log.debug("执行信令消息:{}", message); + final ClientSession session = this.clientSessionManager.session(instance); // 验证请求 final Message value = JSONUtils.toJava(message, Message.class); if(value == null) { log.warn("消息格式错误(解析失败):{}", message); + session.push(this.errorProtocol.build("消息格式错误(解析失败)")); return; } final Header header = value.getHeader(); if(header == null) { log.warn("消息格式错误(没有头部):{}", message); + session.push(this.errorProtocol.build("消息格式错误(没有头部)")); return; } + final String v = header.getV(); final String id = header.getId(); final String sn = header.getSn(); final Integer pid = header.getPid(); - if(id == null || sn == null || pid == null) { - log.warn("消息格式错误(id|sn|pid):{}", message); + if(v == null || id == null || sn == null || pid == null) { + log.warn("消息格式错误(缺失头部关键参数):{}", message); + session.push(this.errorProtocol.build("消息格式错误(缺失头部关键参数)")); return; } // 设置缓存ID @@ -91,14 +96,15 @@ public class ProtocolManager { final Protocol protocol = this.protocolMapping.get(pid); if(protocol == null) { log.warn("不支持的信令协议:{}", message); + session.push(this.errorProtocol.build("不支持的信令协议")); return; } - final ClientSession session = this.clientSessionManager.session(instance); if(protocol instanceof ClientRegisterProtocol) { protocol.execute(sn, value, session); } else if(session.authorized() && sn.equals(session.sn())) { protocol.execute(sn, value, session); } else { + log.warn("终端会话没有授权:{}", message); session.push(this.errorProtocol.build(MessageCode.CODE_3401, "终端会话没有授权")); } } diff --git a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/ShutdownProtocol.java b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/ShutdownProtocol.java index 79165e1..eb6b429 100644 --- a/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/ShutdownProtocol.java +++ b/taoyao-signal/src/main/java/com/acgist/taoyao/signal/protocol/platform/ShutdownProtocol.java @@ -30,6 +30,7 @@ public class ShutdownProtocol extends ProtocolAdapter { if(this.context instanceof ConfigurableApplicationContext context) { log.info("关闭信令服务:{}", sn); if(context.isActive()) { + // 如果需要完整广播可以设置延时 context.close(); } } else {