📚目录
1. WebSocket基础概念深度解析
1.1 什么是WebSocket?深度理解
WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通信的协议。
🎯 核心理念对比:
传统HTTP模式(请求-响应):
客户端 ----请求----> 服务器
客户端 <----响应---- 服务器
[连接关闭,下次需要重新建立]
优点:简单、无状态、易于理解
缺点:无法主动推送、开销大、延迟高
WebSocket模式(持久连接):
客户端 ====握手====> 服务器
客户端 <====双向通信====> 服务器
[连接保持开放状态]
优点:实时双向、低延迟、低开销
缺点:复杂性增加、状态管理需要
1.2 WebSocket解决的核心问题
1.2.1 实时性问题
传统解决方案的局限:
// 1. 轮询 (Polling) - 浪费资源
setInterval(() => {
fetch('/api/check-updates')
.then(response => response.json())
.then(data => {
if (data.hasUpdates) {
updateUI(data);
}
});
}, 1000); // 每秒请求一次,即使没有更新
// 2. 长轮询 (Long Polling) - 复杂且不稳定
function longPoll() {
fetch('/api/long-poll')
.then(response => response.json())
.then(data => {
updateUI(data);
longPoll(); // 递归调用
})
.catch(() => {
setTimeout(longPoll, 5000); // 错误后重试
});
}
// 3. Server-Sent Events (SSE) - 单向通信
const eventSource = new EventSource('/api/events');
eventSource.onmessage = (event) => {
updateUI(JSON.parse(event.data));
};
// 只能服务器向客户端推送,客户端无法主动发送
WebSocket的优势:
// WebSocket - 真正的双向实时通信
const ws = new WebSocket('ws://localhost:8080/realtime');
// 立即接收服务器推送
ws.onmessage = (event) => {
updateUI(JSON.parse(event.data));
};
// 客户端主动发送
ws.send(JSON.stringify({
type: 'user_action',
data: 'some_data'
}));
1.2.2 网络开销问题
HTTP请求开销分析:
GET /api/data HTTP/1.1
Host: example.com
User-Agent: Mozilla/5.0...
Accept: application/json
Cookie: session=abc123...
Authorization: Bearer token...
Cache-Control: no-cache
Connection: close
[总共约800-1500字节的头部信息,仅为获取可能只有几字节的数据]
WebSocket帧开销:
WebSocket最小帧:2字节头部 + 数据
相比HTTP减少95%以上的开销
1.3 WebSocket技术特性详解
1.3.1 全双工通信
// 客户端可以随时发送消息
ws.send('Hello from client at ' + new Date());
// 服务器也可以随时推送消息
// 服务器端代码会在后面详细讲解
1.3.2 协议升级机制
WebSocket通过HTTP升级机制建立连接:
# 第1步:客户端发起升级请求
GET /chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket # 要求升级到WebSocket
Connection: Upgrade # 连接升级
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== # 安全密钥
Sec-WebSocket-Version: 13 # WebSocket版本
Sec-WebSocket-Protocol: chat, superchat # 可选子协议
Sec-WebSocket-Extensions: permessage-deflate # 可选扩展
# 第2步:服务器响应升级
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= # 基于客户端密钥计算的接受密钥
Sec-WebSocket-Protocol: chat # 选择的子协议
# 第3步:协议切换完成,开始WebSocket通信
1.3.3 子协议和扩展
// 指定子协议
const ws = new WebSocket('ws://localhost:8080/chat', ['chat-v1', 'chat-v2']);
// 检查服务器选择的协议
ws.onopen = () => {
console.log('使用的协议:', ws.protocol);
};
2. WebSocket协议技术详解
2.1 数据帧结构深度分析
WebSocket使用帧(Frame)进行数据传输,每个帧包含以下信息:
0 1 2 3
0 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| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
详细字段解释:
字段 | 位数 | 说明 |
---|---|---|
FIN | 1 | 指示这是否是消息的最后一个片段 |
RSV1-3 | 3 | 保留位,必须为0(除非扩展定义了非零值) |
Opcode | 4 | 操作码,定义帧的类型 |
MASK | 1 | 指示载荷数据是否被掩码(客户端到服务器必须为1) |
Payload Length | 7 | 载荷数据长度 |
Extended Payload Length | 16/64 | 扩展载荷长度(当基础长度为126或127时) |
Masking Key | 32 | 掩码密钥(当MASK=1时存在) |
Payload Data | 变长 | 实际传输的数据 |
2.2 操作码详解
操作码 | 值 | 描述 | 用途 |
---|---|---|---|
Continuation | 0x0 | 继续帧 | 分片消息的后续帧 |
Text | 0x1 | 文本帧 | UTF-8编码的文本数据 |
Binary | 0x2 | 二进制帧 | 二进制数据 |
Close | 0x8 | 关闭帧 | 关闭连接 |
Ping | 0x9 | Ping帧 | 心跳检测 |
Pong | 0xA | Pong帧 | 对Ping的响应 |
2.3 消息分片机制
大消息可以分割成多个帧传输:
// 发送大文件的示例
function sendLargeFile(file) {
const chunkSize = 1024 * 64; // 64KB per chunk
const totalChunks = Math.ceil(file.size / chunkSize);
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
const message = {
type: 'file_chunk',
chunkIndex: i,
totalChunks: totalChunks,
fileName: file.name,
data: chunk
};
ws.send(JSON.stringify(message));
}
}
2.4 掩码机制
客户端发送的所有帧都必须使用掩码,防止缓存污染攻击:
// 掩码算法(JavaScript示例,实际由浏览器自动处理)
function maskData(data, maskKey) {
const masked = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
masked[i] = data[i] ^ maskKey[i % 4];
}
return masked;
}
3. WebSocket生命周期与状态管理
3.1 连接状态详解
WebSocket连接有四个状态:
const WebSocketState = {
CONNECTING: 0, // 正在连接
OPEN: 1, // 连接已建立
CLOSING: 2, // 连接正在关闭
CLOSED: 3 // 连接已关闭
};
// 检查连接状态
function checkWebSocketState(ws) {
switch(ws.readyState) {
case WebSocket.CONNECTING:
console.log('正在连接到服务器...');
break;
case WebSocket.OPEN:
console.log('连接已建立,可以发送数据');
break;
case WebSocket.CLOSING:
console.log('连接正在关闭...');
break;
case WebSocket.CLOSED:
console.log('连接已关闭');
break;
}
}
3.2 完整的生命周期管理
class WebSocketManager {
constructor(url, options = {}) {
this.url = url;
this.options = {
reconnectInterval: 1000,
maxReconnectAttempts: 5,
heartbeatInterval: 30000,
...options
};
this.ws = null;
this.reconnectAttempts = 0;
this.heartbeatTimer = null;
this.reconnectTimer = null;
this.listeners = {
open: [],
message: [],
close: [],
error: []
};
}
connect() {
try {
this.ws = new WebSocket(this.url);
this.setupEventHandlers();
} catch (error) {
console.error('WebSocket连接失败:', error);
this.handleReconnect();
}
}
setupEventHandlers() {
this.ws.onopen = (event) => {
console.log('WebSocket连接已建立');
this.reconnectAttempts = 0;
this.startHeartbeat();
this.emit('open', event);
};
this.ws.onmessage = (event) => {
console.log('收到消息:', event.data);
// 处理心跳响应
if (event.data === 'pong') {
console.log('收到心跳响应');
return;
}
this.emit('message', event);
};
this.ws.onclose = (event) => {
console.log('WebSocket连接已关闭:', event.code, event.reason);
this.stopHeartbeat();
this.emit('close', event);
// 非正常关闭时尝试重连
if (event.code !== 1000) {
this.handleReconnect();
}
};
this.ws.onerror = (error) => {
console.error('WebSocket错误:', error);
this.emit('error', error);
};
}
send(data) {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send(typeof data === 'string' ? data : JSON.stringify(data));
return true;
} else {
console.warn('WebSocket未连接,无法发送数据');
return false;
}
}
close(code = 1000, reason = 'Normal closure') {
if (this.ws) {
this.ws.close(code, reason);
}
this.stopHeartbeat();
this.stopReconnect();
}
// 心跳机制
startHeartbeat() {
this.heartbeatTimer = setInterval(() => {
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.send('ping');
}
}, this.options.heartbeatInterval);
}
stopHeartbeat() {
if (this.heartbeatTimer) {
clearInterval(this.heartbeatTimer);
this.heartbeatTimer = null;
}
}
// 重连机制
handleReconnect() {
if (this.reconnectAttempts < this.options.maxReconnectAttempts) {
this.reconnectAttempts++;
const delay = this.options.reconnectInterval * Math.pow(2, this.reconnectAttempts - 1);
console.log(`${delay}ms后尝试第${this.reconnectAttempts}次重连`);
this.reconnectTimer = setTimeout(() => {
this.connect();
}, delay);
} else {
console.error('达到最大重连次数,停止重连');
}
}
stopReconnect() {
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = null;
}
}
// 事件监听器
on(event, callback) {
if (this.listeners[event]) {
this.listeners[event].push(callback);
}
}
emit(event, data) {
if (this.listeners[event]) {
this.listeners[event].forEach(callback => callback(data));
}
}
}
// 使用示例
const wsManager = new WebSocketManager('ws://localhost:8080/chat', {
reconnectInterval: 2000,
maxReconnectAttempts: 10,
heartbeatInterval: 25000
});
wsManager.on('open', () => {
console.log('连接成功!');
});
wsManager.on('message', (event) => {
const data = JSON.parse(event.data);
handleMessage(data);
});
wsManager.connect();
3.3 关闭代码详解
WebSocket关闭时会返回状态码:
代码 | 名称 | 描述 |
---|---|---|
1000 | Normal Closure | 正常关闭 |
1001 | Going Away | 端点离开(如页面关闭) |
1002 | Protocol Error | 协议错误 |
1003 | Unsupported Data | 不支持的数据类型 |
1004 | Reserved | 保留 |
1005 | No Status Received | 未收到状态码 |
1006 | Abnormal Closure | 异常关闭 |
1007 | Invalid Frame Payload Data | 无效的帧载荷数据 |
1008 | Policy Violation | 违反策略 |
1009 | Message Too Big | 消息过大 |
1010 | Mandatory Extension | 强制扩展 |
1011 | Internal Error | 内部错误 |
1015 | TLS Handshake | TLS握手失败 |
ws.onclose = (event) => {
switch(event.code) {
case 1000:
console.log('正常关闭');
break;
case 1001:
console.log('页面离开或服务器关闭');
break;
case 1006:
console.log('连接异常断开,可能需要重连');
break;
default:
console.log(`连接关闭,代码: ${event.code}, 原因: ${event.reason}`);
}
};
4. Spring Boot WebSocket完整实现
4.1 项目结构与依赖
4.1.1 完整的项目结构
websocket-chat/
├── src/
│ ├── main/
│ │ ├── java/
│ │ │ └── com/
│ │ │ └── example/
│ │ │ └── websocketchat/
│ │ │ ├── WebSocketChatApplication.java
│ │ │ ├── config/
│ │ │ │ ├── WebSocketConfig.java
│ │ │ │ ├── WebSocketStompConfig.java
│ │ │ │ └── WebSocketSecurityConfig.java
│ │ │ ├── controller/
│ │ │ │ ├── ChatController.java
│ │ │ │ └── WebController.java
│ │ │ ├── handler/
│ │ │ │ └── ChatWebSocketHandler.java
│ │ │ ├── model/
│ │ │ │ ├── ChatMessage.java
│ │ │ │ ├── ChatUser.java
│ │ │ │ └── MessageType.java
│ │ │ ├── service/
│ │ │ │ ├── ChatService.java
│ │ │ │ ├── UserSessionService.java
│ │ │ │ └── MessageService.java
│ │ │ ├── listener/
│ │ │ │ └── WebSocketEventListener.java
│ │ │ └── util/
│ │ │ └── WebSocketSessionManager.java
│ │ └── resources/
│ │ ├── static/
│ │ │ ├── css/
│ │ │ │ └── chat.css
│ │ │ ├── js/
│ │ │ │ ├── chat.js
│ │ │ │ └── websocket-manager.js
│ │ │ └── index.html
│ │ └── application.yml
│ └── test/
└── pom.xml
4.1.2 详细的Maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.example</groupId>
<artifactId>websocket-chat</artifactId>
<version>1.0.0</version>
<name>websocket-chat</name>
<description>WebSocket聊天室完整项目</description>
<properties>
<java.version>17</java.version>
</properties>
<dependencies>
<!-- Spring Boot WebSocket启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- Spring Boot Web启动器 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot安全启动器(可选) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<optional>true</optional>
</dependency>
<!-- Spring Boot数据JPA(可选,用于消息持久化) -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<optional>true</optional>
</dependency>
<!-- H2数据库(开发测试用) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- JSON处理 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- 日志处理 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- 开发工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.2 配置文件详解
4.2.1 application.yml配置
# application.yml
server:
port: 8080
servlet:
context-path: /
spring:
application:
name: websocket-chat
# WebSocket相关配置
we