目录
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 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| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
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 {
@Override
public 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<>());
/**
* 连接建立成功调用的方法
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
sessions.add(session);
System.out.println("连接建立:" + session.getId());
// 向新连接的客户端发送欢迎消息
session.sendMessage(new TextMessage("欢迎连接WebSocket服务器!"));
}
/**
* 接收到消息时调用的方法
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message)
throws Exception {
String payload = message.getPayload();
System.out.println("收到消息:" + payload);
// 广播消息给所有连接的客户端
broadcastMessage("用户说:" + payload);
}
/**
* 连接关闭后调用的方法
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status)
throws Exception {
sessions.remove(session);
System.out.println("连接关闭:" + session.getId());
}
/**
* 传输错误时调用的方法
*/
@Override
public 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