WebSocket详细教程 - SpringBoot实战指南
目录
1. 什么是WebSocket
1.1 基本概念
1.2 形象比喻
1.3 核心特点
2. WebSocket vs HTTP对比
2.1 通信方式对比
2.2 详细对比表
3. WebSocket协议原理
3.1 连接建立过程
步骤1:客户端发起握手请求
步骤2:服务器响应握手
步骤3:协议升级完成
3.2 数据帧格式
4. SpringBoot中的WebSocket
4.1 添加依赖
4.2 SpringBoot中的两种实现方式
5. 基础实现示例
5.1 原生WebSocket实现
5.1.1 WebSocket配置类
5.1.2 WebSocket处理器
5.1.3 前端JavaScript代码
6. STOMP协议详解
6.1 什么是STOMP
6.2 STOMP的优势
6.3 STOMP配置
6.3.1 配置类
6.3.2 消息控制器
6.3.3 消息实体类
6.3.4 WebSocket事件监听器
6.3.5 前端STOMP客户端
7. 完整项目实战
7.1 项目结构
7.2 主要业务逻辑
7.2.1 聊天服务类
7.2.2 增强的聊天控制器
8. 高级特性
8.1 身份验证和授权
8.1.1 WebSocket安全配置
8.2 消息持久化
8.2.1 消息实体
8.2.2 消息仓库
8.3 集群支持
8.3.1 Redis消息代理配置
9. 最佳实践
9.1 连接管理
9.1.1 连接池管理
9.2 错误处理和重连机制
9.2.1 客户端重连逻辑
9.3 性能优化
9.3.1 消息限流
9.3.2 消息压缩
10. 常见问题与解决方案
10.1 连接问题
问题1:WebSocket连接被防火墙阻止
问题2:跨域问题
10.2 性能问题
问题1:内存泄漏
问题2:消息堆积
10.3 安全问题
问题1:未授权访问
问题2:消息内容过滤
🎯 总结
学习建议
适用场景
1. 什么是WebSocket
1.1 基本概念
WebSocket是一种网络通信协议,提供了全双工通信能力。简单来说:
- 传统HTTP:客户端问一次,服务器答一次(单向请求-响应)
- WebSocket:客户端和服务器可以随时互相发送消息(双向实时通信)
1.2 形象比喻
想象一下:
- HTTP就像写信:你写信给朋友,朋友收到后回信给你,一来一回
- WebSocket就像打电话:连接建立后,双方都可以随时说话,实时交流
1.3 核心特点
特点 | 说明 |
---|---|
持久连接 | 一次握手,持续通信 |
全双工 | 双方都可主动发送消息 |
低延迟 | 无需重复建立连接 |
轻量级 | 数据传输开销小 |
2. WebSocket vs HTTP对比
2.1 通信方式对比
HTTP通信流程:
客户端 ──请求──> 服务器
客户端 <──响应── 服务器
客户端 ──请求──> 服务器 (新的连接)
客户端 <──响应── 服务器WebSocket通信流程:
客户端 ──握手请求──> 服务器
客户端 <──握手响应── 服务器
客户端 <──────────> 服务器 (持续双向通信)
2.2 详细对比表
对比项 | HTTP | WebSocket |
---|---|---|
连接性质 | 无状态、短连接 | 有状态、长连接 |
通信方向 | 单向(请求-响应) | 双向(全双工) |
服务器推送 | 不支持 | 原生支持 |
协议开销 | 每次请求都有完整HTTP头 | 握手后开销极小 |
实时性 | 差(需要轮询) | 优秀(即时推送) |
适用场景 | 一般Web应用 | 实时应用(聊天、游戏等) |
3. WebSocket协议原理
3.1 连接建立过程
步骤1:客户端发起握手请求
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
步骤2:服务器响应握手
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
步骤3:协议升级完成
连接从HTTP协议升级为WebSocket协议,开始全双工通信。
3.2 数据帧格式
WebSocket使用帧(Frame)来传输数据:
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
4. SpringBoot中的WebSocket
4.1 添加依赖
<dependencies><!-- SpringBoot Web Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- SpringBoot WebSocket Starter --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>
</dependencies>
4.2 SpringBoot中的两种实现方式
方式 | 特点 | 适用场景 |
---|---|---|
原生WebSocket | 底层API,灵活度高 | 简单点对点通信 |
STOMP协议 | 高级消息协议,功能丰富 | 复杂消息系统 |
5. 基础实现示例
5.1 原生WebSocket实现
5.1.1 WebSocket配置类
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {@Overridepublic void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {// 注册WebSocket处理器registry.addHandler(new MyWebSocketHandler(), "/websocket").setAllowedOrigins("*"); // 允许跨域}
}
5.1.2 WebSocket处理器
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {// 存储所有WebSocket会话private static final Set<WebSocketSession> sessions = Collections.synchronizedSet(new HashSet<>());/*** 连接建立成功调用的方法*/@Overridepublic void afterConnectionEstablished(WebSocketSession session) throws Exception {sessions.add(session);System.out.println("连接建立:" + session.getId());// 向新连接的客户端发送欢迎消息session.sendMessage(new TextMessage("欢迎连接WebSocket服务器!"));}/*** 接收到消息时调用的方法*/@Overrideprotected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {String payload = message.getPayload();System.out.println("收到消息:" + payload);// 广播消息给所有连接的客户端broadcastMessage("用户说:" + payload);}/*** 连接关闭后调用的方法*/@Overridepublic void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {sessions.remove(session);System.out.println("连接关闭:" + session.getId());}/*** 传输错误时调用的方法*/@Overridepublic void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {System.out.println("传输错误:" + exception.getMessage());sessions.remove(session);}/*** 广播消息给所有客户端*/private void broadcastMessage(String message) {synchronized (sessions) {for (WebSocketSession session : sessions) {try {if (session.isOpen()) {session.sendMessage(new TextMessage(message));}} catch (Exception e) {e.printStackTrace();}}}}
}
5.1.3 前端JavaScript代码
<!DOCTYPE html>
<html>
<head><title>WebSocket测试</title>
</head>
<body><div><input type="text" id="messageInput" placeholder="输入消息..."><button onclick="sendMessage()">发送</button><button onclick="connect()">连接</button><button onclick="disconnect()">断开</button></div><div id="messages"></div><script>let socket = null;function connect() {socket = new WebSocket('ws://localhost:8080/websocket');socket.onopen = function(event) {console.log('WebSocket连接已建立');addMessage('已连接到服务器');};socket.onmessage = function(event) {console.log('收到消息:', event.data);addMessage('服务器:' + event.data);};socket.onclose = function(event) {console.log('WebSocket连接已关闭');addMessage('连接已断开');};socket.onerror = function(error) {console.log('WebSocket错误:', error);addMessage('连接错误:' + error);};}function sendMessage() {const input = document.getElementById('messageInput');if (socket && socket.readyState === WebSocket.OPEN) {socket.send(input.value);addMessage('我:' + input.value);input.value = '';} else {alert('请先连接WebSocket');}}function disconnect() {if (socket) {socket.close();}}function addMessage(message) {const messages = document.getElementById('messages');cons