本片文章是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 服务器。
但是,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
除@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 注解。
在服务启动过程中因为我们在类上添加了@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添加到管理集合中。
@OnMessage:当服务器接收到客户端发送的消息时调用,可以在此方法中处理接收到的消息。
@OnClose:当WebSocket连接关闭时调用,可以在此方法中释放资源或将Session从管理集合中移除。
@OnError:当发生错误时调用,可以在此方法中处理异常或错误。
消息编解码:如果需要,可以通过@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方法需要在判断登录成功后在进行调用。