WebSocket详解(三):WebSocket具体在工程中是如何应用的

405 阅读6分钟

file_24bb69b396594b3f8ef156c116d1d1df.png



本片文章是WebSocket基础的系列讲解第三篇文章,按照学习顺序,建议先学习:

006 WebSocket详解(一):初识WebSocket前世今生

007 WebSocket详解(二):揭开webSocket的神秘面纱

本篇文章就工程中如何使用websocket做一个简单的分享。

一、服务端对websocket的支持

webchat服务端我们使用了SpringBoot框架,而SpringBoot框架的底层****提供了对WebSocket的内置支持,并且整合了JSR 356(Java API for WebSocket)规范。 @ServerEndpoint注解是JSR 356规范的一部分,用于声明一个服务器端的WebSocket端点。

关于这里的JSR356我个人认为没必要深究,可以简单的理解为是websocket在java中如果想要支持,需要符合一定的要求。

这里大家只需要重点关注在spring中如何使用 @ServerEndpoint注解来声明一个websocket端点即可,以及这个注解在工程中是如何起作用的(这个注解的底层实现原理)。

@ServerEndpoint

@ServerEndpoint 注解是 Java 规范中的一部分,而不是 Spring 框架特有的。这个注解用于在 Java 应用程序中定义 WebSocket 端点,使得我们可以很容易的来创建一个 WebSocket 服务器。

file_858353d31f744f04a8ac55d6f6ecb6a5.png



但是,Spring 框架提供了对 @ServerEndpoint 的支持,使得在 Spring 应用程序中可以方便地使用这个注解来创建和管理 WebSocket 端点。Spring 通过提供自动配置和集成支持,可以在 Spring 应用程序中无缝地使用 @ServerEndpoint

首先我们需要在工程中引入相关依赖jar包,具体是在pom.xml中添加了一下依赖配置,通过maven包管理器从远程maven仓库下载依赖包到我们本地:

<!-- 引入 websocket 依赖类-->
<!-- Springboot对websocket的能力支持-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>
<!-- ServerEndpoint注解来自于javax.websocket-api这个包 -->
		<dependency>
			<groupId>javax.websocket</groupId>
			<artifactId>javax.websocket-api</artifactId>
			<version>1.1</version>
			<scope>provided</scope>
		</dependency>
<!-- tomcat服务队websocket的支持 -->
		<dependency>
			<groupId>org.apache.tomcat.embed</groupId>
			<artifactId>tomcat-embed-websocket</artifactId>
			<version>9.0.33</version>
		</dependency>

webchat程序中定义websocket端点类:

com.webchat.controller.client.ChatWebSocket.java

file_9f8c79e1b4704e2fa1dff7f3b49cb81f.png



除@ServerEndpoint注解之外,还有几个核心的注解:@OnOpen、@OnError、@OnClose、@OnMessage,分别对应链接创建成功、失败、关闭、收到消息四个核心场景。具体每个注解是能力是如何实现的,我们一步步来通过几个问题来揭开服务端如何实现websocket端点能力:

为什么添加了@ **ServerEndpoint注解,程序能够知道这是一个websocket的端点?

这要得益于SpringBoot的自动装配能力,可以自动检测WebSocket相关依赖,并根据这些依赖自动配置WebSocket支持。这意味着,如果我们的项目中包含了Spring Boot的WebSocket starter(spring-boot-starter-websocket),Spring Boot会自动配置WebSocket环境,包括@ServerEndpoint注解的解析。

这个过程文章这里我们简单介绍下,后面通过视频的形式带着大家来一步步了解底层的实现细节:

*我们在启动类中使用添加了@SpringBootApplication, *@SpringBootApplication 是一个组合注解,它包含了 @EnableAutoConfiguration 和 @Configuration 注解。

file_357960e5014341ed92c50bfca95c7f9c.png



在服务启动过程中因为我们在类上添加了@Component注解,这使得程序启动后会去扫描相关的类,创建对应的bean,当然如果发现bean有@ServerEndpoint,Springboot框架会:

注册WebSocket端点:Spring Boot通过ServerEndpointExporter类来注册@ServerEndpoint注解的类作为WebSocket端点。这个类会将带有@ServerEndpoint注解的类暴露为WebSocket服务器端点,使得客户端可以通过指定的URL连接到这些端点。

ws://localhost:8101/ws/chat/{userId}

创建和管理WebSocket会话:每个连接到@ServerEndpoint指定URL的客户端都会创建一个新的会话(Session),并且通常会为每个会话创建一个新的服务器端对象实例。这个对象负责管理与特定客户端的通信。

处理WebSocket生命周期事件:@ServerEndpoint注解的类中可以定义几个生命周期方法,包括:

@OnOpen:当WebSocket连接建立时调用,可以在此方法中初始化资源或将新的Session添加到管理集合中。

file_16deeb022aad45888fbe44df061394c6.png



@OnMessage:当服务器接收到客户端发送的消息时调用,可以在此方法中处理接收到的消息。

file_58ba8c15796442b694df40ffd3c936dc.png



@OnClose:当WebSocket连接关闭时调用,可以在此方法中释放资源或将Session从管理集合中移除。

file_2a9d280c216c4599b1935f094c4ad0ff.png



@OnError:当发生错误时调用,可以在此方法中处理异常或错误。

file_9b7133ecad214d6bb24583af09b76e92.png



消息编解码:如果需要,可以通过@ServerEndpoint注解的encoders和decoders属性指定自定义的消息编码器和解码器,以便在WebSocket消息传递过程中对消息进行编码和解码。

自动配置:如果@ServerEndpoint注解的类上添加了@Component注解,Spring会自动将其作为Bean进行管理,无需显式注册。

二、客户端websocket连接创建、管理

客户端实现比较简单,我们首先需要明白一个问题:目前主流的浏览器都已经支持了websocket,也正如此在js中创建ws连接、以及ws管理中不需要引入第三方js库:

代码:/resources/templates/client/chat.html

    var webSocket;
    // 心跳检测的间隔时间(毫秒)
    const heartbeatInterval = 30000;
    // 重连尝试的时间间隔(毫秒)
    var reconnectInterval = 5000;
    // 最大重连尝试间隔时间
    var maxReconnectInterval = 300000; // 5分钟
    var heartbeatTimer;

/**
     * 初始化websocket对象及ws管理
     */
function initWebSocket() {
        
        if ("WebSocket" in window) {
            webSocket = new WebSocket("ws://"+wsHost+":"+wsPort+"/ws/chat/" + userId);
            webSocket.onopen = function () {
                console.log("已经连通了websocket");
            };
            // 发送心跳函数
            function sendHeartbeat() {
                if (webSocket.readyState === WebSocket.OPEN) {
                    webSocket.send('heartbeat');
                }
            }
            // 重连函数
            function reconnect() {
                reconnectTimer = setTimeout(function () {
                    webSocket = null;
                    initWebSocket();
                    reconnectInterval = Math.min(maxReconnectInterval, reconnectInterval * 2);
                }, reconnectInterval);
            }
            // 连接打开时
            webSocket.onopen = function () {
                console.log('WebSocket connection established');
                // 连接成功后启动心跳检测
                heartbeatTimer = setInterval(sendHeartbeat, heartbeatInterval);
                // 重置重连间隔时间
                reconnectInterval = 5000;
            };
            //接收后台服务端的消息
            webSocket.onmessage = function (evt) {
                if (evt.data == "ok") {
                    // 跳过心跳检测
                    console.log("心跳检测成功!");
                    return;
                }
                // 处理消息 TODO
            };
            //连接关闭的回调事件
            webSocket.onclose = function () {
                console.log("连接已关闭...");
                clearInterval(heartbeatTimer);
                clearTimeout(reconnectTimer);
                reconnect(); // 调用重连函数
            };
        } else {
            // 浏览器不支持 WebSocket
            alert("您的浏览器不支持 WebSocket!");
        }
    }

这段代码比较简单,这里就不做过多介绍了,大家看注释应该是可以看明白的。但是需要关注:

1、是相比网上大多数的ws客户端代码,我们这里支持了心跳检测和重链机制,这是未了保证当遇到网络波动等问题时,可以保证连接自动重连,长时间一直可以使用。

2、因为我们这里的连接是通过登录用户id实现隔离的,所以initWebSocket方法需要在判断登录成功后在进行调用。