一、简介
-
因为用前端实现的客户端,比方说小程序,网络不稳定,会经常断,所以考虑用java实现客户端,稳定。
-
java版的重连机制确实花费了好多时间才正好。
-
重连的时候刚开始没有加同步,导致定时器发心跳频繁的时候上次还没有完全创建完就又创建了一个客户端,加同步避免了。
-
sendMsg的时候之前没有加超时,可能有同时存在多个建立连接占用资源的隐患,加了超时。额 此处限制被我在生产环境去掉了,因为这个时间不好控制,短了的话会一直连不上。。。暂时再考虑这块有没有必要处理。
-
还有websocket比较恶心的是 每次error的时候都开一个新线程去通知,如果你一直失败,就等着cpu爆炸吧。
-
WebSocket有五种状态:NOT_YET_CONNECTED、CONNECTING、OPEN、CLOSING、CLOSED, 只有not_yet_connected的时候才可以connect, 一旦connect之后,状态改变了,就无法再connect了
。
二、代码实现
2.1 pom依赖
<!--websocket客户端依赖-->
<dependency>
<groupId>org.java-websocket</groupId>
<artifactId>Java-WebSocket</artifactId>
<version>1.3.8</version>
</dependency>
2.2 WebSocketClientFactory
package com.example.demo.server;
import com.alibaba.fastjson.JSONObject;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;
import org.springframework.stereotype.Component;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
/**
* WebSocketClient
* @author qzz
* @date 2024/4/25
*/
@Component
@Slf4j
@Data
public class WebSocketClientFactory {
public static final String outCallWebSockertUrl = "ws://IP:端口";
private WebSocketClient outCallWebSocketClientHolder;
/**
* 创建websocket对象
*
* @return WebSocketClient
* @throws URISyntaxException
*/
private WebSocketClient createNewWebSocketClient() throws URISyntaxException {
WebSocketClient webSocketClient = new WebSocketClient(new URI(outCallWebSockertUrl)) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
//发送token 认证
Map<String,String> data =new HashMap<>();
data.put("token", "填写token");
send(JSONObject.toJSONString(data));
}
@Override
public void onMessage(String response) {
log.info("-------- 接收到服务端数据: " + response + "--------");
}
@Override
public void onClose(int i, String s, boolean b) {
log.info("关闭连接");
retryOutCallWebSocketClient();
}
@Override
public void onError(Exception e) {
log.info("连接异常");
retryOutCallWebSocketClient();
}
};
webSocketClient.connect();
return webSocketClient;
}
/**
* 项目启动或连接失败的时候打开新链接,进行连接认证
* 需要加同步,不然会创建多个连接
*/
public synchronized WebSocketClient retryOutCallWebSocketClient() {
try {
// 关闭旧的websocket连接, 避免占用资源
WebSocketClient oldOutCallWebSocketClientHolder = this.getOutCallWebSocketClientHolder();
if (null != oldOutCallWebSocketClientHolder) {
log.info("关闭旧的websocket连接");
oldOutCallWebSocketClientHolder.close();
}
log.info("打开新的websocket连接,并进行认证");
WebSocketClient webSocketClient = this.createNewWebSocketClient();
//发送token 认证
Map<String,String> data =new HashMap<>();
data.put("token", "填写token");
this.sendMsg(webSocketClient, JSONObject.toJSONString(data));
// 每次创建新的就放进去
this.setOutCallWebSocketClientHolder(webSocketClient);
return webSocketClient;
} catch (URISyntaxException e) {
e.printStackTrace();
log.error(e.getMessage());
}
return null;
}
/**
* 发送消息
* 注意: 要加超时设置,避免很多个都在同时超时占用资源
*
* @param webSocketClient 指定的webSocketClient
* @param message 消息
*/
public void sendMsg(WebSocketClient webSocketClient, String message) {
log.info("websocket向服务端发送消息,消息为:{}", message);
long startOpenTimeMillis = System.currentTimeMillis();
while (!webSocketClient.getReadyState().equals(WebSocket.READYSTATE.OPEN)) {
log.debug("正在建立通道,请稍等");
long currentTimeMillis = System.currentTimeMillis();
if(currentTimeMillis - startOpenTimeMillis >= 5000) {
log.error("超过5秒钟还未打开连接,超时,不再等待");
return;
}
}
webSocketClient.send(message);
}
}
2.2 设置启动类
package com.example.demo;
import com.example.demo.server.WebSocketClientFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements ApplicationRunner {
@Autowired
private WebSocketClientFactory webSocketClientFactory;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(ApplicationArguments args) {
// 项目启动的时候打开websocket连接
webSocketClientFactory.retryOutCallWebSocketClient();
}
}
三、测试
3.1 控制类
package com.example.demo.controller;
import com.example.demo.server.WebSocketClientFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 测试类
* @author qzz
* @date 2024/4/25
*/
@RestController
public class TestController {
@Autowired
private WebSocketClientFactory webSocketClientFactory;
@PostMapping("/send")
public void send(){
String msg = "{\"node_num\": \"HCBB24400011\",\"control_data\": { \"control_cmd\": \"FG_control\",\"talk_channel\": \"1234\" }, \"id\": \"17138435580aad\" }";
webSocketClientFactory.sendMsg(webSocketClientFactory.getOutCallWebSocketClientHolder(), msg);
}
}
3.2 启动操作日志
"D:\Program Files\Java\jdk-17.0.3.1\bin\java.exe" -agentlib:jdwp=transport=dt_socket,address=127.0.0.1:61815,suspend=y,server=n -XX:TieredStopAtLevel=1 -Dspring.output.ansi.enabled=always -Dcom.sun.management.jmxremote -Dspring.jmx.enabled=true -Dspring.liveBeansView.mbeanDomain -Dspring.application.admin.enabled=true "-Dmanagement.endpoints.jmx.exposure.include=*" -javaagent:C:\Users\Admin\AppData\Local\JetBrains\IntelliJIdea2023.3\captureAgent\debugger-agent.jar -Dfile.encoding=UTF-8 -classpath "F:\work-zakj\websocket\websocket-example\demo\target\classes;D:\mvnrepository\org\springframework\boot\spring-boot-starter-web\3.2.5\spring-boot-starter-web-3.2.5.jar;D:\mvnrepository\org\springframework\boot\spring-boot-starter\3.2.5\spring-boot-starter-3.2.5.jar;D:\mvnrepository\org\springframework\boot\spring-boot\3.2.5\spring-boot-3.2.5.jar;D:\mvnrepository\org\springframework\boot\spring-boot-autoconfigure\3.2.5\spring-boot-autoconfigure-3.2.5.jar;D:\mvnrepository\org\springframework\boot\spring-boot-starter-logging\3.2.5\spring-boot-starter-logging-3.2.5.jar;D:\mvnrepository\ch\qos\logback\logback-classic\1.4.14\logback-classic-1.4.14.jar;D:\mvnrepository\ch\qos\logback\logback-core\1.4.14\logback-core-1.4.14.jar;D:\mvnrepository\org\apache\logging\log4j\log4j-to-slf4j\2.21.1\log4j-to-slf4j-2.21.1.jar;D:\mvnrepository\org\apache\logging\log4j\log4j-api\2.21.1\log4j-api-2.21.1.jar;D:\mvnrepository\org\slf4j\jul-to-slf4j\2.0.13\jul-to-slf4j-2.0.13.jar;D:\mvnrepository\jakarta\annotation\jakarta.annotation-api\2.1.1\jakarta.annotation-api-2.1.1.jar;D:\mvnrepository\org\yaml\snakeyaml\2.2\snakeyaml-2.2.jar;D:\mvnrepository\org\springframework\boot\spring-boot-starter-json\3.2.5\spring-boot-starter-json-3.2.5.jar;D:\mvnrepository\com\fasterxml\jackson\core\jackson-databind\2.15.4\jackson-databind-2.15.4.jar;D:\mvnrepository\com\fasterxml\jackson\core\jackson-annotations\2.15.4\jackson-annotations-2.15.4.jar;D:\mvnrepository\com\fasterxml\jackson\core\jackson-core\2.15.4\jackson-core-2.15.4.jar;D:\mvnrepository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.15.4\jackson-datatype-jdk8-2.15.4.jar;D:\mvnrepository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.15.4\jackson-datatype-jsr310-2.15.4.jar;D:\mvnrepository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.15.4\jackson-module-parameter-names-2.15.4.jar;D:\mvnrepository\org\springframework\boot\spring-boot-starter-tomcat\3.2.5\spring-boot-starter-tomcat-3.2.5.jar;D:\mvnrepository\org\apache\tomcat\embed\tomcat-embed-core\10.1.20\tomcat-embed-core-10.1.20.jar;D:\mvnrepository\org\apache\tomcat\embed\tomcat-embed-el\10.1.20\tomcat-embed-el-10.1.20.jar;D:\mvnrepository\org\apache\tomcat\embed\tomcat-embed-websocket\10.1.20\tomcat-embed-websocket-10.1.20.jar;D:\mvnrepository\org\springframework\spring-web\6.1.6\spring-web-6.1.6.jar;D:\mvnrepository\org\springframework\spring-beans\6.1.6\spring-beans-6.1.6.jar;D:\mvnrepository\io\micrometer\micrometer-observation\1.12.5\micrometer-observation-1.12.5.jar;D:\mvnrepository\io\micrometer\micrometer-commons\1.12.5\micrometer-commons-1.12.5.jar;D:\mvnrepository\org\springframework\spring-webmvc\6.1.6\spring-webmvc-6.1.6.jar;D:\mvnrepository\org\springframework\spring-aop\6.1.6\spring-aop-6.1.6.jar;D:\mvnrepository\org\springframework\spring-context\6.1.6\spring-context-6.1.6.jar;D:\mvnrepository\org\springframework\spring-expression\6.1.6\spring-expression-6.1.6.jar;D:\mvnrepository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;D:\mvnrepository\org\springframework\spring-core\6.1.6\spring-core-6.1.6.jar;D:\mvnrepository\org\springframework\spring-jcl\6.1.6\spring-jcl-6.1.6.jar;D:\mvnrepository\org\java-websocket\Java-WebSocket\1.3.8\Java-WebSocket-1.3.8.jar;D:\mvnrepository\org\projectlombok\lombok\1.18.28\lombok-1.18.28.jar;D:\mvnrepository\com\alibaba\fastjson\1.2.75\fastjson-1.2.75.jar;D:\Program Files\JetBrains\IntelliJ IDEA 2023.3.2\lib\idea_rt.jar" com.example.demo.DemoApplication
Connected to the target VM, address: '127.0.0.1:61815', transport: 'socket'
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.2.5)
2024-04-25T16:04:11.046+08:00 INFO 22112 --- [demo] [ main] com.example.demo.DemoApplication : Starting DemoApplication using Java 17.0.3.1 with PID 22112 (F:\work-zakj\websocket\websocket-example\demo\target\classes started by Admin in F:\work-zakj\websocket\websocket-example\demo)
2024-04-25T16:04:11.049+08:00 INFO 22112 --- [demo] [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default"
2024-04-25T16:04:11.543+08:00 INFO 22112 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port 8080 (http)
2024-04-25T16:04:11.551+08:00 INFO 22112 --- [demo] [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2024-04-25T16:04:11.551+08:00 INFO 22112 --- [demo] [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.20]
2024-04-25T16:04:11.580+08:00 INFO 22112 --- [demo] [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2024-04-25T16:04:11.580+08:00 INFO 22112 --- [demo] [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 502 ms
2024-04-25T16:04:11.791+08:00 INFO 22112 --- [demo] [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-04-25T16:04:11.797+08:00 INFO 22112 --- [demo] [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.978 seconds (process running for 1.33)
2024-04-25T16:04:11.799+08:00 INFO 22112 --- [demo] [ main] c.e.demo.server.WebSocketClientFactory : 打开新的websocket连接,并进行认证
2024-04-25T16:04:11.834+08:00 INFO 22112 --- [demo] [ main] c.e.demo.server.WebSocketClientFactory : websocket向服务端发送消息,消息为:{"id":"17138435580aad","token":"etwetwetwetwetwetwetwetwetwetwetwetwetwetwewtetwet"}
2024-04-25T16:04:12.096+08:00 INFO 22112 --- [demo] [ctReadThread-41] c.e.demo.server.WebSocketClientFactory : -------- 接收到服务端数据: {"code": 0, "message": "token success", "id": "17138435580aad"}--------
2024-04-25T16:04:12.133+08:00 INFO 22112 --- [demo] [ctReadThread-41] c.e.demo.server.WebSocketClientFactory : -------- 接收到服务端数据: {"code": 205,"message": "control cmd error ","id": ""}--------
2024-04-25T16:04:18.405+08:00 INFO 22112 --- [demo] [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2024-04-25T16:04:18.405+08:00 INFO 22112 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2024-04-25T16:04:18.406+08:00 INFO 22112 --- [demo] [nio-8080-exec-2] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2024-04-25T16:04:18.424+08:00 INFO 22112 --- [demo] [nio-8080-exec-2] c.e.demo.server.WebSocketClientFactory : websocket向服务端发送消息,消息为:{"node_num": "HCBB24400011","control_data": { "control_cmd": "FG_control","talk_channel": "1234" }, "id": "17138435580aad" }
2024-04-25T16:04:18.465+08:00 INFO 22112 --- [demo] [ctReadThread-41] c.e.demo.server.WebSocketClientFactory : -------- 接收到服务端数据: {"code": 0,"message": "control device success","id": "17138435580aad"}--------
三、完整代码
点击此处下载