实现websocket心跳检测,断线重连机制

一、 概述

WebSocket心跳检测和断线重连机制在实时网络通信中扮演着至关重要的角色,它们确保了即时通讯的连贯性、可靠性和高效性。

心跳检测:通过定期发送小数据包(心跳包)来检查WebSocket连接状态,确认客户端和服务器之间的连接是否仍然活跃。
断线重连:在WebSocket连接因网络问题或其他原因意外断开时,自动尝试重新建立连接的过程。

二、 代码实现

首先,我们需要引入websocket依赖,以便能够使用对应功能:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
  </dependency>

创建一个 WebSocketServer 类,用于处理 WebSocket 连接和消息。

WebSocket服务器端点,用于与客户端建立WebSocket连接并进行通信。它继承了RedisSubscriber类,并实现了OnOpenOnCloseOnMessageOnErrorWebSocket生命周期的方法。以下是该类的主要功能:
建立WebSocket连接:通过@ServerEndpoint注解指定服务器端点的路径为/ws/{sid},其中sid为保留字段。在@OnOpen方法中,与客户端的连接建立成功时执行相应的逻辑,如将当前WebSocketServer实例添加到webSocketSet集合中,增加在线数量,并启动定时任务。
关闭WebSocket连接:在@OnClose方法中,当客户端连接关闭时执行相应的逻辑,如从webSocketSet集合中移除当前WebSocketServer实例,减少在线数量。
处理客户端发送的消息:在@OnMessage方法中,接收客户端发送的消息并进行处理。如果接收到的消息是心跳包,则直接返回;否则,根据消息内容执行相应的业务逻辑,并通过sendMessage方法将处理结果发送给客户端。
异常处理:在@OnError方法中,处理WebSocket连接发生错误时的逻辑,如记录错误日志。
群发消息:提供sendInfo方法用于向所有连接的客户端发送消息。
指定会话推送:提供sendInfo方法用于向指定会话的客户端发送消息。
数据处理:根据接收到的消息内容,调用相应的业务服务,获取数据并转换为JSON格式,最后将数据发送给客户端。
该类使用了CopyOnWriteArraySet来存储所有连接的客户端的WebSocketServer实例,使用ScheduledExecutorService来定时发送心跳包和处理业务逻辑。同时,它还利用AtomicBooleanAtomicInteger来控制数据推送的状态和在线数量的线程安全操作。

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(); // 重连
   });
}
  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值