什么是 WebSocket
Websocket 是一种 协议 ,用于在 客户端 和 服务器 之间建立持久的 双向通信连接 ,广泛应用于需要实时数据交换 的应用程序,例如在线聊天、实时游戏、股票行情等。是从 HTML5 开始支持的一种网页端和服务端保持长连接的 消息推送机制
WebSocket 协议 与 HTTP 协议有什么区别呢?
HTTP 协议通常使用在 "一问一答" 的应用场景中:
客户端向服务器发送一个 HTTP 请求,服务器给客户端返回一个 HTTP 响应
上述情况中,服务器属于被动的一方,若客户端不主动发起请求,服务器就无法主动给客户端响应
那,若需要服务器主动给客户端发送消息,该如何实现呢?
此时就需要 消息推送机制
消息推送 (Push Notification)是一种通信机制 ,允许服务器向客户端主动发送消息或通知,而无需客户端的请求。消息推送使得服务器可以在有重要信息或事件发生时,主动将消息推送给用户

当 user1 发送消息时,user1 的客户端就需要给服务器发送消息,告诉服务器需要将消息发送给 user2,此时,服务器就需要立即将消息转发给 user2,及时将消息同步到 user2 的客户端
此时,若基于 HTTP 来实现上述消息推送效果,就需要基于 "轮询" 的机制
(也就是说 user2 的客户端需要不停地向服务器发送请求)
由于 user2 并不知道 user1 什么时候发送消息,因此,只能不停的向服务器发送请求,直到 user1 发送消息,user2 才能获取到消息
显而易见,上述的 轮询 操作,开销较大,成本也较高

- 若轮询间隔时间较长,user1 发送消息时,user2 不能及时获取到消息,及时性较差
- 若轮询间隔时间较短,此时及时性得到了提升,但 user2 会浪费更多的机器资源(如带宽)
因此,消息推送机制 更适合实现服务器主动给客户端发送消息的场景,而 WebSocket是实现消息推送的主要方式
接下来,我们就来进一步学习 WebSocket 的报文格式
WebSocket 报文格式
WebSocket 协议是一个 应用层协议 ,其下层是基于TCP 协议的

WebSocket报文主要由以下几个部分组成:
标识字段(FIN, RSV1, RSV2, RSV3)
操作码(Opcode)
掩码标识符(Mask)
数据长度(Payload length)
扩展数据(Extension data)
Masking-key
负载数据(Payload data)
上述大部分内容,我们只需要了解即可,我们主要了解负载数据
负载数据(Payload data):实际传输的数据
其长度由 Payload length 指定,内容可以是文本、二进制数据或其他类型的数据,具体取决于帧的操作码
在学习了 WebSocket 的报文格式 后,我们继续学习 WebSocket 的握手过程,也就是建立连接的过程
WebSocket 的握手过程
WebSocket 的握手过程是建立 WebSocket 连接的关键步骤 ,使用 HTTP 协议进行初始化, 但在成功建立连接后,通信会切换到 WebSocket 协议 ,整个握手过程包括:客户端向服务器发起 HTTP 请求,服务器响应后,连接被升级为 WebSocket 连接
步骤:
1. 客户端发送 WebSocket 握手请求(HTTP 请求)
2. 服务器回应 WebSocket 握手响应(HTTP 响应)
3. 连接升级
客户端发送 WebSocket 握手请求
首先,客户端会尝试与服务器建立 WebSocket 连接,使用HTTP 请求 向服务器发送 WebSocket 握手请求,在这个请求中会带有特殊的 header 信息,表示客户端希望建立 WebSocket 连接,并告知服务器它支持 WebSocket 协议
客户端请求:

服务器回应 WebSocket 握手响应
若服务器支持 WebSocket 协议,并且准备好升级连接,服务器会返回一个 HTTP 101 状态码响应,表示协议切换成功。响应中会包含 WebSocket 协议相关信息

连接升级
当客户端收到服务器返回的 Sec-WebSocket-Accept 时,客户端就知道连接升级成功,WebSocket 连接已经建立。此时,HTTP 协议转化为 WebSocket 协议,连接升级完成,双方可以通过 WebSocket 协议进行双向通信
总而言之,WebSocket 握手实际上是一个基于 HTTP 的协议升级的过程:客户端发起 HTTP 请求,服务器确认并返回一个 HTTP 响应,协议成功切换为 WebSocket
在握手过程中,Sec-WebSocket-Key 和 Sec-WebSocket-Accept 的配对是 WebSocket 握手是否成功的重要验证机制,确保连接的安全性
当握手成功后,WebSocket 连接将保持打开状态,双方可以在连接上进行任意的数据交换,直到一方主动关闭连接
在了解了相关知识后,接下来,我们就可以实现 WebSocket 相关代码了
代码实现示例
服务器代码
由于 Spring 中内置了 WebSocket,因此可以直接使用
引入依赖
在创建项目时添加

代码编写
实现一个类并继承 TextWebSocketHandler类:并重写下面的方法

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketMessage;
import org.springframework.web.socket.WebSocketSession;
@Component
public class TextWebSocketHandler extends org.springframework.web.socket.handler.TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
super.afterConnectionEstablished(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
super.handleTextMessage(session, message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
super.handleTransportError(session, exception);
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
super.afterConnectionClosed(session, status);
}
}
afterConnectionEstablished() :该方法在 WebSocket 连接成功建立后会被调用,可以在方法中进行初始化操作,如 进行身份验证handleTextMessage():该方法是 TextWebSocketHandler 中最重要的方法,用于处理接收到的文本消息,对该方法进行重写,以在接收到客户端发送的文本消息时执行特定逻辑
handleTransportError():当 WebSocket 连接出现错误时,该方法会被调用,可以在方法中进行错误处理,如 关闭连接 或 记录错误日志
afterConnectionClosed():该方法在 WebSocket 连接关闭时会被调用,可以在方法中做一些清理工作,如 释放资源
package com.example.demo;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;
@Component
public class TextWebSocketHandler extends org.springframework.web.socket.handler.TextWebSocketHandler {
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
System.out.println("websocket连接成功");
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("成功接受到消息"+message.getPayload());
session.sendMessage(message);
}
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
System.out.println("连接异常");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
System.out.println("连接断开");
}
}
我们来进一步了解一下 TextWebSocketHandler类
TextWebSocketHandler
TextWebSocketHandler 是 Spring WebSocket 模块中的一个类,用于处理 WebSocket 连接中的文本消息,继承自 WebSocketHandler 接口,专门处理文本消息,并提供了一些方法来处理连接、消息传输、断开等 WebSocket 相关事件
TextWebSocketHandler 中的主要方法:
handleTextMessage(WebSocketSession session, TextMessage message)
作用:
当客户端发送文本消息时,调用该方法处理接收到的文本消息
参数:
WebSocketSession session: 当前 WebSocket 会话的上下文,提供了与客户端的连接信息
TextMessage message:客户端发送的文本消息,包含消息的内容(通 message.getPayload() 方法获取)
afterConnectionEstablished(WebSocketSession session)
作用:
当 WebSocket 连接成功建立后,调用此方法,可以用来执行初始化操作,例如 记录日志、进行身份验证等
参数:
WebSocketSession session :当前的 WebSocket 会话信息
与 handleTextMessage 的区别:
afterConnectionEstablished 方法在 WebSocket 连接建立时调用 ,用于执行连接建立后的初始化任务;而 handleTextMessage 用于处理客户端发送的消息,用于实时交互的消息处理
afterConnectionClosed(WebSocketSession session, CloseStatus status)
作用:
当 WebSocket 连接被关闭时,调用此方法,可以用来进行资源清理、记录日志等相关操作
参数:
WebSocketSession session:当前的 WebSocket 会话信息
CloseStatus status:关闭连接的状态信息,包含关闭的原因等
handleTransportError(WebSocketSession session, Throwable exception)
作用:
当 WebSocket 连接出现错误时,调用此方法,可以用于错误处理、日志记录等相关操作
参数:
WebSocketSession session:当前的 WebSocket 会话信息
Throwable exception:发生的异常
配置 WebSocket
TextWebSocketHandler 只是 WebSocket 处理的一个部分,要想使用 WebSocket,还需要配置 WebSocket 接口,注册 WebSocket 端点
创建 WebSocketConfig 类来对 WebSocket 进行相关配置,并实现 WebSocketConfigurer 接口:
package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.net.http.WebSocket;
@Configuration
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private TextWebSocketHandler textWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(textWebSocketHandler,"/test");
}
}
WebSocketConfigurer :
是 Spring WebSocket 中用于配置 WebSocket 的接口,包含一个方法 registerWebSocketHandlers(),用于注册 WebSocket 端点并设置相关配置。通过这个接口,可以将 WebSocket 处理器(如上述实现的 TestWebSocketHandler)与特定的 URL 端点进行绑定
@Configuration 注解:
是 Spring Framework 中的一种标识注解,标识当前类作为配置类,用于替代传统的 XML 配置文件
@EnableWebSocket 注解:
用于启动 Spring 的 WebSocket 功能,告诉 Spring 容器需要在应用中配置 WebSocket 服务,允许通过 Spring 配置类来管理 WebSocket 连接和消息的处理
配置完成后,服务器端代码就基本完成了,我们继续实现客户端代码
客户端代码
实现一个输入框,用于输入要发送的内容;一个按钮,用于提交消息:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试webSocket页面</title>
</head>
<body>
<input type="text" id="message">
<button id="submit">提交</button>
</body>
</html>
接下来,创建 WebSocket 实例,并挂载回调函数
<script>
var socket = new WebSocket("ws://127.0.0.1:8080/test");
socket.onopen = function(){
console.log("连接成功");
}
socket.onmessage = function(event){
console.log("收到消息",event.data);
}
socket.onclose = function(){
console.log("连接关闭");
}
socket.onerror = function(){
console.log("连接错误");
}
</script>
可以发现前端实现的回调函数,与后端实现的相关方法是相匹配的
分别用于:建立连接 、接收消息 、处理异常 和连接关闭
最后,实现点击事件:
var button = document.getElementById("submit");
button.onclick = function(){
var message = document.getElementById("message");
socket.send(message.value);
}
完整实现:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>测试webSocket页面</title>
</head>
<body>
<input type="text" id="message">
<button id="submit">提交</button>
<script>
var socket = new WebSocket("ws://127.0.0.1:8080/test");
socket.onopen = function(){
console.log("连接成功");
}
socket.onmessage = function(event){
console.log("收到消息",event.data);
}
socket.onclose = function(){
console.log("连接关闭");
}
socket.onerror = function(){
console.log("连接错误");
}
var button = document.getElementById("submit");
button.onclick = function(){
var message = document.getElementById("message");
socket.send(message.value);
}
</script>
</body>
</html>


