Spring Cloud WebSocket 秒断问题排查与解决

Spring Cloud WebSocket 秒断问题排查与解决:网关子协议不一致导致

目录

  1. 前言
  2. 问题背景
  3. 代码展示
  4. 问题分析 (续~)
  5. 解决方法
  6. 完整代码
  7. 总结

前言

在微服务架构项目中,我遇到了一个关于 WebSocket 技术应用的难题。本文旨在分享我遇到的 WebSocket 连接 “秒断” 问题,以及如何通过分析和调试解决它。作为一名后端开发者,我承认自己对源码的理解还不够深入,日常开发中我常常依赖于“搜索引擎+AI”来解决问题。但是,当这些工具也无能为力时,我便需要深入源码、查阅资料、向社区求助。希望通过这篇文章,可以帮助遇到同样问题的开发者。

问题背景

在 Spring Cloud 框架下,我尝试使用 WebSocket 技术实现一个终端功能。由于涉及到实时交互,WebSocket 自然成为首选方案。起初,我通过搜索和复制粘贴(CV)代码,并进行了一些修改,快速搭建了服务。服务启动后,一切看起来正常。然而,我很快发现 WebSocket 连接建立后会立即断开,这就是我所说的“秒断”问题。

我尝试进行断点调试,深入源码分析,但复杂的代码逻辑让我感到困惑。由于项目进度紧张,我不得不采用一种临时方案:绕过网关,让前端直接连接后端的“终端服务”。令人惊讶的是,这种方式运行良好,没有任何问题。这让我确信问题出在网关上。

我使用的是 Spring Cloud Gateway 作为微服务的网关。当我尝试通过网关连接后端的 WebSocket 服务时,连接会立即断开。当我直接通过 IP 地址和端口,绕过网关连接后端的 WebSocket 服务时,连接则一切正常。这让我开始怀疑网关在处理 WebSocket 连接时,是否做了某些操作或更改,导致了连接的“秒断”? 如果大家有相关经验,欢迎分享解答。

代码展示

Maven 依赖

引入 spring-boot-starter-websocket 依赖即可使用 Spring Boot 提供的 WebSocket 功能。

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

WebSocket 配置类

import com.ruoyi.terminal.webssh.socket.WebSocketHandler;
import com.ruoyi.terminal.webssh.socket.WebSocketInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * WebSocket配置
 *
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig extends ServerEndpointConfig.Configurator implements WebSocketConfigurer {

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/ssh")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*");
        registry
                .addHandler(webSocketHandler, "/sock-ssh")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*")
                // 开启sockJs支持
                .withSockJS();
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取websocket固定头
        List<String> list = request.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL);
        if (list != null) {
            String s = list.get(0);
            String[] split = s.split(",");
            List<String> resList = new ArrayList<>();
            resList.add(split[0]);
            //返回的Sec-WebSocket-Protocol只需要从Sec-WebSocket-Protocol取第一个返回就行,否则会握手失败
            response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, resList);
        }
        //头返回给前端
        super.modifyHandshake(sec, request, response);
    }
}

说明:

  • @EnableWebSocket: 启用 WebSocket 功能。

  • registerWebSocketHandlers: 注册 WebSocket 处理程序,并配置拦截器和允许的请求源。

    • /ssh: 直接的 WebSocket 连接端点。
    • /sock-ssh: 支持 SockJS 的 WebSocket 连接端点。
  • ServerEndpointExporter: 用于注册 @ServerEndpoint 注解的 Bean。

  • modifyHandshake: 用于修改握手响应头,解决一些兼容性问题(如 Sec-WebSocket-Protocol)。

WebSocket 处理器

import com.ruoyi.common.constant.Const;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

/**
 *  WebSocket 处理器
 *
 */
@Component
public class WebSocketHandler implements org.springframework.web.socket.WebSocketHandler {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketHandler.class);

    /**
     * 用户连接之前的回调函数
     *
     * @param session WebSocket会话对象
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        logger.info("用户[ {} ]成功连接!", session.getAttributes().get(Const.SESSION_KEY));
    }

    /**
     * 收到WebSocket消息
     *
     * @param session WebSocket会话对象
     * @param message 接收到的消息
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
        if (message instanceof TextMessage) {
            logger.info("用户[ {} ]发送命令: {}", session.getAttributes().get(Const.SESSION_KEY), message.getPayload());
        }
    }

    /**
     * 出现错误时的回调
     *
     * @param session   WebSocket会话对象
     * @param exception 错误信息
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        logger.error("用户[ {} ]出现错误", session.getAttributes().get(Const.SESSION_KEY), exception);
    }

    /**
     * 断开连接后的回调
     *
     * @param session     WebSocket会话对象
     * @param closeStatus 关闭状态对象
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
        logger.info("用户[ {} ]已断开连接", session.getAttributes().get(Const.SESSION_KEY));
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

说明:

  • WebSocketHandler: 用于处理 WebSocket 连接的生命周期事件和消息。
  • afterConnectionEstablished: WebSocket 连接建立后触发,记录连接信息。
  • handleMessage: 接收到客户端发送的消息时触发,处理消息。
  • handleTransportError: WebSocket 连接发生错误时触发,记录错误信息。
  • afterConnectionClosed: WebSocket 连接关闭后触发,记录断开连接信息。
  • supportsPartialMessages: 设置是否支持部分消息,这里设置为 false 表示不支持。

WebSocket 拦截器

import com.ruoyi.common.constant.Const;
import com.ruoyi.common.utils.RandomUtil;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import java.util.Map;

/**
 * WebSocket 拦截器
 *
 */
@Component
public class WebSocketInterceptor implements HandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                                   Map<String, Object> attributes) {
        if (request instanceof ServletServerHttpRequest) {
            attributes.put(Const.SESSION_KEY, RandomUtil.randomString(16));
            return true;
        }
        return false;
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
                               Exception exception) {
    }
}

说明:

  • HandshakeInterceptor: WebSocket 握手拦截器,用于在握手前后执行自定义逻辑。
  • beforeHandshake: 在握手前执行,检查请求类型,并为每个 WebSocket 会话生成一个随机 Session ID 并存储到 attributes 中。
  • afterHandshake: 在握手后执行,这里为空,未做任何处理。

问题分析 (续~)

在我们的项目中,秒断问题是因为前端在发送连接请求时,传递了子协议 Sec-WebSocket-Protocol,而后端在收到请求后会验证前后端子协议是否一致,不一致则断开连接,也就是标题的‘秒断’。

WebSocket 协议本身没有请求头,所以传参只能通过路径拼接,后端通过路径来获取参数;或者使用子协议 Sec-WebSocket-Protocol 来传递参数。我目前了解的只有这两种方式。

网关报错信息:

在网关的日志中,可以看到如下类似的报错信息(具体错误信息可能因网关配置而异):

2024-01-01 10:00:00.000 ERROR [reactor-http-nio-4] … WebSocketClientHandshakeException: Invalid subprotocol. Actual: null. Expected one of: protocol

Sec-WebSocket-Protocol 的作用:

Sec-WebSocket-Protocol 是 WebSocket 握手协议的一部分,用于指定 WebSocket 连接所使用的子协议。子协议允许在同一个 WebSocket 连接上进行多种不同的协议交互。如果在握手过程中,客户端和服务端指定的子协议不一致,连接就会被拒绝。

错误分析:

从上面的错误信息来看,网关在处理 WebSocket 连接时,没有正确处理 Sec-WebSocket-Protocol。Spring Cloud Gateway 作为中间层,会转发客户端的请求,如果客户端发送了 Sec-WebSocket-Protocol,而网关没有正确透传或处理,就会导致后端服务收到的 Sec-WebSocket-Protocol 与客户端发送的不一致,从而导致握手失败,连接被秒断。当客户端发送 Sec-WebSocket-Protocol 为 protocol 的时候,在经过网关之后到达服务端的 Sec-WebSocket-Protocol 头信息变为了 null, 导致握手失败。

解决方法

WebSocket 有两种常用的写法,对应的解决方法也略有不同:

方法一:原生注解(Tomcat 内嵌)

如果你使用 @ServerEndpoint 注解来实现 WebSocket 服务,并且使用了内嵌的 Tomcat 容器,可以通过以下方式配置子协议:

  • @ServerEndpoint: 将当前的类定义成一个 WebSocket 服务器端点,注解的值将用于监听用户连接的终端访问 URL 地址,客户端可以通过这个 URL 来连接到 WebSocket 服务器端点。
  • @OnOpen: 当 WebSocket 建立连接成功后会触发这个注解修饰的方法。
  • @OnClose: 当 WebSocket 建立的连接断开后会触发这个注解修饰的方法。
  • @OnMessage: 当客户端发送消息到服务端时,会触发这个注解修改的方法。
  • @OnError: 当 WebSocket 建立连接时出现异常会触发这个注解修饰的方法。

只需在 WebSocketServer 类中的 @ServerEndpoint 注解中加入 subprotocols = {“protocol”} 配置子协议即可:

@Component
@ServerEndpoint(value = "/chat/{uid}", subprotocols = {"protocol"}, configurator = WebSocketConfig.class)
public class WebSocketServer {
   // ...
}

subprotocols = {“protocol”} 中的 “protocol” 需要与前端传递的 Sec-WebSocket-Protocol 的值保持一致。

方法二:Spring 封装

如果你使用 Spring 提供的 WebSocketHandler 和 WebSocketConfigurer 来实现 WebSocket 服务,可以在 WebSocketConfig 配置类的 registerWebSocketHandlers 方法中设置子协议。

以下是修改后的 WebSocketConfig 类,其中使用了自定义的 SubProtocolHandshakeInterceptor 拦截器来处理子协议:

import com.ruoyi.terminal.webssh.socket.WebSocketHandler;
import com.ruoyi.terminal.webssh.socket.WebSocketInterceptor;
import com.ruoyi.terminal.webssh.config.SubProtocolHandshakeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * WebSocket配置
 *
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig extends ServerEndpointConfig.Configurator implements WebSocketConfigurer {

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/ssh")
                .addInterceptors(new WebSocketInterceptor())
                 .addInterceptors(new SubProtocolHandshakeInterceptor(Collections.singletonList("protocol")))
                .setAllowedOrigins("*");
        registry
                .addHandler(webSocketHandler, "/sock-ssh")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*")
                // 开启sockJs支持
                .withSockJS();
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取websocket固定头
        List<String> list = request.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL);
        if (list != null) {
            String s = list.get(0);
            String[] split = s.split(",");
            List<String> resList = new ArrayList<>();
            resList.add(split[0]);
            //返回的Sec-WebSocket-Protocol只需要从Sec-WebSocket-Protocol取第一个返回就行,否则会握手失败
            response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, resList);
        }
        //头返回给前端
        super.modifyHandshake(sec, request, response);
    }
}

我们通过 addInterceptors(new SubProtocolHandshakeInterceptor(Collections.singletonList(“protocol”))) 添加 SubProtocolHandshakeInterceptor ,用于处理 Sec-WebSocket-Protocol。 并且在 modifyHandshake 方法中,只取Sec-WebSocket-Protocol 的第一个协议返回,避免握手失败。

完整代码

WebSocketConfig

import com.ruoyi.terminal.webssh.socket.WebSocketHandler;
import com.ruoyi.terminal.webssh.socket.WebSocketInterceptor;
import com.ruoyi.terminal.webssh.config.SubProtocolHandshakeInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * WebSocket配置
 *
 */
@Configuration
@EnableWebSocket
public class WebSocketConfig extends ServerEndpointConfig.Configurator implements WebSocketConfigurer {

    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/ssh")
                .addInterceptors(new WebSocketInterceptor())
                 .addInterceptors(new SubProtocolHandshakeInterceptor(Collections.singletonList("protocol")))
                .setAllowedOrigins("*");
        registry
                .addHandler(webSocketHandler, "/sock-ssh")
                .addInterceptors(new WebSocketInterceptor())
                .setAllowedOrigins("*")
                // 开启sockJs支持
                .withSockJS();
    }

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        //获取websocket固定头
        List<String> list = request.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL);
        if (list != null) {
            String s = list.get(0);
            String[] split = s.split(",");
            List<String> resList = new ArrayList<>();
            resList.add(split[0]);
            //返回的Sec-WebSocket-Protocol只需要从Sec-WebSocket-Protocol取第一个返回就行,否则会握手失败
            response.getHeaders().put(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, resList);
        }
        //头返回给前端
        super.modifyHandshake(sec, request, response);
    }
}

SubProtocolHandshakeInterceptor

package com.ruoyi.terminal.webssh.config;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.HandshakeInterceptor;
import javax.websocket.server.HandshakeRequest;
import java.util.List;
import java.util.Map;
/**
 * 子协议拦截器,用于处理websocket的Sec-WebSocket-Protocol
 */
public class SubProtocolHandshakeInterceptor implements HandshakeInterceptor {
    private List<String> subProtocols;
    /**
     * 构造函数
     * @param subProtocols 支持的子协议集合
     */
    public SubProtocolHandshakeInterceptor(List<String> subProtocols) {
        this.subProtocols = subProtocols;
    }

   /**
    * 在握手之前执行, 这里不做任何处理, 主要逻辑在 `afterHandshake` 中实现
    */
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        return true;
    }

   /**
    * 在握手之后执行,用于处理子协议
    *  @param request  请求信息
    * @param response  响应信息
    * @param wsHandler websocket处理器
    * @param exception 异常信息
    */
    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) {
        List<String> requestedProtocols = request.getHeaders().get(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL);
        //如果请求头包含Sec-WebSocket-Protocol
        if (requestedProtocols != null && !requestedProtocols.isEmpty()) {
            // 遍历支持的子协议
            for(String subProtocol: subProtocols){
             //   System.out.println(requestedProtocols.get(0));
              //  System.out.println(subProtocol);
                // 如果请求头包含支持的子协议,则设置响应头,并结束循环
                if(requestedProtocols.get(0).contains(subProtocol)){
                    response.getHeaders().set(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL, subProtocol);
                    return;
                }
            }
            // 如果不支持,则移除 Sec-WebSocket-Protocol 响应头
           // response.getHeaders().remove(HandshakeRequest.SEC_WEBSOCKET_PROTOCOL);
        }
    }
}

这段代码实现了 HandshakeInterceptor 接口,用于在 WebSocket 握手过程中处理 Sec-WebSocket-Protocol。

  • beforeHandshake 方法: 在握手之前执行,这里不做任何处理,直接返回 true。
  • afterHandshake 方法: 在握手之后执行,用于处理子协议。首先获取请求头中 Sec-WebSocket-Protocol 的值,如果请求头包含 Sec-WebSocket-Protocol, 则会遍历支持的子协议,如果请求头中包含支持的子协议,则设置响应头,并结束循环。如果不支持,则移除 Sec-WebSocket-Protocol 响应头。这里设置响应头后,就不会再报 Invalid subprotocol 错误了

WebSocketHandler

import com.ruoyi.common.constant.Const;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;

/**
 *  WebSocket 处理器
 *
 */
@Component
public class WebSocketHandler implements org.springframework.web.socket.WebSocketHandler {

    private static final Logger logger = LoggerFactory.getLogger(WebSocketHandler.class);

    /**
     * 用户连接之前的回调函数
     *
     * @param session WebSocket会话对象
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        logger.info("用户[ {} ]成功连接!", session.getAttributes().get(Const.SESSION_KEY));
    }

    /**
     * 收到WebSocket消息
     *
     * @param session WebSocket会话对象
     * @param message 接收到的消息
     */
    @Override
    public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) {
        if (message instanceof TextMessage) {
            logger.info("用户[ {} ]发送命令: {}", session.getAttributes().get(Const.SESSION_KEY), message.getPayload());
        }
    }

    /**
     * 出现错误时的回调
     *
     * @param session   WebSocket会话对象
     * @param exception 错误信息
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) {
        logger.error("用户[ {} ]出现错误", session.getAttributes().get(Const.SESSION_KEY), exception);
    }

    /**
     * 断开连接后的回调
     *
     * @param session     WebSocket会话对象
     * @param closeStatus 关闭状态对象
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {
        logger.info("用户[ {} ]已断开连接", session.getAttributes().get(Const.SESSION_KEY));
    }

    @Override
    public boolean supportsPartialMessages() {
        return false;
    }
}

这段代码实现了 org.springframework.web.socket.WebSocketHandler 接口,用于处理 WebSocket 连接的生命周期事件和消息。

  • afterConnectionEstablished: WebSocket 连接建立后触发,记录连接信息。
  • handleMessage: 接收到客户端发送的消息时触发,处理消息。
  • handleTransportError: WebSocket 连接发生错误时触发,记录错误信息。
  • afterConnectionClosed: WebSocket 连接关闭后触发,记录断开连接信息。
  • supportsPartialMessages: 设置是否支持部分消息,这里设置为 false 表示不支持。

总结

通过本次问题排查,我深入理解了 WebSocket 子协议的作用,以及网关在 WebSocket 连接中的角色。为了避免类似问题再次发生,总结以下几点:

  • 确保前端和后端 WebSocket 的子协议一致,这是解决 “秒断” 问题的关键。
  • 理解 Spring Cloud Gateway 等网关在处理 WebSocket 连接时可能会对 Sec-WebSocket-Protocol 进行处理,从而导致握手失败。
  • 在开发过程中,要充分利用浏览器开发工具和日志信息来排查问题。
  • 对于 Spring Boot 提供的 WebSocket 支持,可以通过 SubProtocolHandshakeInterceptor 等方式来处理子协议。
  • 如果使用了 ServerEndpoint 原生注解,需要添加 subprotocols 属性

希望本文能够帮助遇到类似问题的开发者。欢迎大家在评论区留言交流,共同进步!

### HTML5 WebSocket 开连接解决方案 在开发基于HTML5的应用程序时,WebSocket提供了强大的实时通信能力。然而,在某些情况下,比如用户刷新页面或网络不稳定时,WebSocket连接可能会意外中。 为了处理这种情况,可以采用自动重连机制来维持稳定的连接状态[^1]。具体实现方法如下: #### 实现自动重连功能 通过设置定时器监听`onclose`事件,并尝试重新建立新的WebSocket实例直到成功为止。下面是一个简单的JavaScript代码片段展示了如何实现这一逻辑: ```javascript let ws; function createWebSocket() { const url = 'ws://yourserver.com/socket'; try { ws = new WebSocket(url); // 连接打开后的回调函数 ws.onopen = function () { console.log('Connected to server'); }; // 接收到消息后的回调函数 ws.onmessage = function (event) { console.log(`Received message from server: ${event.data}`); }; // 错误发生时的回调函数 ws.onerror = function (error) { console.error(`WebSocket error observed:`, error); }; // 当连接关闭时触发此函数并启动重试机制 ws.onclose = function () { console.warn('Connection closed, retrying...'); setTimeout(createWebSocket, 3000); // 延迟三后再次尝试连接 }; } catch (e) { console.error(e.message); setTimeout(createWebSocket, 3000); // 如果创建失败也延迟三后再试一次 } } // 初始化WebSocket连接 createWebSocket(); ``` 此外,还可以考虑优化上述方案以提高用户体验,例如增加最大重试次数限制、指数退避算法调整等待时间间隔等策略[^2]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值