一、 概述
WebSocket心跳检测和断线重连机制在实时网络通信中扮演着至关重要的角色,它们确保了即时通讯的连贯性、可靠性和高效性。
心跳检测:通过定期发送小数据包(心跳包)来检查WebSocket连接状态,确认客户端和服务器之间的连接是否仍然活跃。
断线重连:在WebSocket连接因网络问题或其他原因意外断开时,自动尝试重新建立连接的过程。
二、 代码实现
首先,我们需要引入websocket依赖,以便能够使用对应功能:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
创建一个 WebSocketServer 类,用于处理 WebSocket 连接和消息。
WebSocket
服务器端点,用于与客户端建立WebSocket
连接并进行通信。它继承了RedisSubscriber
类,并实现了OnOpen
、OnClose
、OnMessage
和OnError
等WebSocket
生命周期的方法。以下是该类的主要功能:
建立WebSocket
连接:通过@ServerEndpoint
注解指定服务器端点的路径为/ws/{sid}
,其中sid
为保留字段。在@OnOpen
方法中,与客户端的连接建立成功时执行相应的逻辑,如将当前WebSocketServer
实例添加到webSocketSet
集合中,增加在线数量,并启动定时任务。
关闭WebSocket连接:在@OnClose
方法中,当客户端连接关闭时执行相应的逻辑,如从webSocketSet
集合中移除当前WebSocketServer
实例,减少在线数量。
处理客户端发送的消息:在@OnMessage
方法中,接收客户端发送的消息并进行处理。如果接收到的消息是心跳包,则直接返回;否则,根据消息内容执行相应的业务逻辑,并通过sendMessage
方法将处理结果发送给客户端。
异常处理:在@OnError
方法中,处理WebSocket
连接发生错误时的逻辑,如记录错误日志。
群发消息:提供sendInfo
方法用于向所有连接的客户端发送消息。
指定会话推送:提供sendInfo
方法用于向指定会话的客户端发送消息。
数据处理:根据接收到的消息内容,调用相应的业务服务,获取数据并转换为JSON
格式,最后将数据发送给客户端。
该类使用了CopyOnWriteArraySet
来存储所有连接的客户端的WebSocketServer
实例,使用ScheduledExecutorService
来定时发送心跳包和处理业务逻辑。同时,它还利用AtomicBoolean
和AtomicInteger
来控制数据推送的状态和在线数量的线程安全操作。
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.redis.RedisSubscriber;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.lang.Object;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
/**
* WebSocket服务类,用于与客户端建立WebSocket连接并进行通信。
* 继承自RedisSubscriber,实现特定的订阅和处理逻辑。
*/
@Slf4j
@Component
@ServerEndpoint(value = "/ws/{sid}")
public class WebSocketServer extends RedisSubscriber {
/**
* ObjectMapper
*/
private static final ObjectMapper objectMapper = new ObjectMapper();
/**
* 记录在线客户端的数量。
*/
private static final AtomicInteger onlineCount = new AtomicInteger(0);
/**
* 存放每个客户端对应的 WebSocketServer 对象
*/
private static CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 心跳报文
*/
private static final String HEARTBEAT_PACKETS = "The heartbeat packets";
/**
* 当WebSocket连接打开时调用。
*
* @param session WebSocket会话对象
* @param sid 保留字段
*/
@OnOpen
public void onOpen(Session session, @PathParam("sid") String sid) {
// 保留字段
String id = sid;
this.session = session;
// 加入set中
webSocketSet.add(this);
// 在线数加1
onlineCount.getAndIncrement();
System.out.println("新连接,连接总数" + webSocketSet.size() + "---" + onlineCount.toString());
}
/**
* 当WebSocket连接关闭时调用。
*/
@OnClose
public void onClose() {
// 从客户端集合中移除当前实例
webSocketSet.remove(this);
// 在线数量减1
onlineCount.getAndDecrement();
System.out.println("断开连接,连接总数" + webSocketSet.size() + "---" + onlineCount.toString());
}
/**
* 当WebSocket连接发生错误时调用。
*
* @param session WebSocket会话对象
* @param error 错误异常
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("[历史数据回放] - WS 异常断开", session, error);
}
/**
* 当收到客户端消息时调用。
*
* @param message 客户端发送的消息
* @param session WebSocket会话对象
*/
@OnMessage
public void onMessage(String message, Session session) {
if (HEARTBEAT_PACKETS.equals(message)) {
log.debug("[消息订阅] - 心跳.");
return;
}
xxxxxx...........
}
/**
* 群发消息到所有连接的客户端。
*
* @param message 要发送的消息
*/
public static void sendInfo(String message) {
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
log.error("[NVR 数据对接] - 数据推送异常, 数据: [{}].", message, e);
continue;
}
}
}
/**
* 向指定会话的客户端发送消息。
*
* @param session 目标会话
* @param message 要发送的消息
*/
public static void sendInfo(Session session, String message) {
for (WebSocketServer item : webSocketSet) {
try {
if (null != session && item.session.equals(session)) {
item.sendMessage(message);
}
} catch (IOException e) {
log.error("[数据对接] - 数据推送异常, 数据: [{}].", message, e);
continue;
}
}
}
/**
* 发送消息到客户端。
*
* @param message 要发送的消息内容
* @throws IOException 如果发送失败
*/
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message);
}
}
基于
Vue3 + ts
的WebSocket连接,实现心跳检测、断线重连的功能
// 心跳消息
const heartBeatMes = ref('The heartbeat packets');
// WebSocket实例
const webSocket = ref<WebSocket>();
// WebSocket连接状态
const webSocketState = ref(false);
// 心跳配置
const heartBeat = reactive( {
time: 5 * 1000, // 心跳时间间隔
timeout: 3 * 1000, // timeout:心跳超时间隔
reconnect: 10 * 1000 // 断线重连时间
});
// 重连计时器
const reconnectTimer = ref<NodeJS.Timeout>();
// 页面关闭标志
const isManualClose = ref(false);
vue画面生命周期
onMounted(() => { connectWebSocket(); }); onUnmounted(() => { isManualClose.value = true; console.log('页面关闭..........'); webSocket.value?.close(); });
1.
startHeartBeat
函数用于发送心跳消息。/** * 心跳检测 * * 通过定时器,在指定的时间间隔后发送心跳消息。 * 调用heartbeatDetectionStatus函数处理检测状态。 * * @param {number} time - 心跳检测的时间间隔,单位为毫秒。 */ function startHeartBeat(time: number) { setTimeout(() => { webSocket.value?.send(heartBeatMes.value); console.log('心跳检测..........'); heartbeatDetectionStatus(); }, time); }
2.
heartbeatDetectionStatus
函数用于检测WebSocket的心跳状态。/** * 实现心跳检测和自动重连机制。 * 若是手动关闭画面,则不再尝试重新连接 * */ function heartbeatDetectionStatus() { setTimeout(() => { // 检查WebSocket连接状态 if (webSocketState.value) { // 连接正常,启动心跳检测 startHeartBeat(heartBeat.time); return; } else { console.log('心跳无响应,已断线..........'); // 尝试关闭WebSocket连接 try { webSocket.value?.close(); } catch (e) { // 异常处理,避免关闭连接时的错误影响后续逻辑 } // 如果不是手动关闭连接,则尝试重新连接 if (!isManualClose.value) { reconnectWebSocket(); } } }, heartBeat.timeout); }
3.
reconnectWebSocket
函数用于重新连接WebSocket。/** * 重连WebSocket * * @returns {void} */ function reconnectWebSocket() { console.log('重新连接..........'); reconnectTimer.value = setTimeout(() => { // 当WebSocket实例不存在时,创建新的连接 if (!webSocket.value) { connectWebSocket(); } // 当WebSocket实例存在且重连定时器正在运行时,清除定时器并尝试再次连接 if (webSocket.value && reconnectTimer.value) { // 防止多个websocket同时执行 clearTimeout(reconnectTimer.value); connectWebSocket(); } }, heartBeat.reconnect); }
4.
connectWebSocket
函数用于连接WebSocket。/** * 连接WebSocke。 */ function connectWebSocket() { // 初始化WebSocket连接 // const baseUrl = import.meta.env.VITE_APP_BASE_WS as string webSocket.value = new WebSocket('ws://127.0.0.1:8080/ws/aa'); console.log("连接中..........") init(); }
5.
init
函数用于初始化。/** * 初始化WebSocket连接,并设置相应的事件监听器。 * 负责管理WebSocket的生命周期,包括连接打开、消息接收、连接关闭和错误处理。 */ function init() { // 当WebSocket连接打开时,设置连接状态为true,并根据配置启动心跳机制 webSocket.value?.addEventListener('open', () => { //socket状态设置为连接,做为后面的断线重连的拦截器 webSocketState.value = true; // 是否启动心跳机制 heartBeat && heartBeat.time ? startHeartBeat(heartBeat.time) : ''; console.log('连接已开启..........'); }); // 当收到WebSocket消息时,进行相应处理 webSocket.value?.addEventListener('message', async (event: MessageEvent<string>) => { const message = JSON.parse(event.data); console.log('接收到消息..........'); }); // 当WebSocket连接关闭时,设置连接状态为false webSocket.value?.addEventListener('close', (event: CloseEvent) => { webSocketState.value = false; console.log('已断开连接..........'); }); // 当WebSocket连接发生错误时,设置连接状态为false并尝试重新连接 webSocket.value?.addEventListener('error', (event: Event) => { webSocketState.value = false; console.log('连接发生了错误..........'); reconnectWebSocket(); // 重连 }); }