WebSocket
→ 返回 计算机网络
WebSocket 是基于 TCP 的全双工通信协议,通过 HTTP 握手升级建立持久连接,服务端可主动推送消息。
与 HTTP 对比
| 特性 | HTTP | WebSocket |
|---|---|---|
| 连接模式 | 请求-响应,短连接 | 持久连接,全双工 |
| 消息方向 | 客户端发起 | 双向,服务端可主动推送 |
| 头部开销 | 每次请求携带完整头部 | 建立后帧头极小(2~10 字节) |
| 适用场景 | 普通接口调用 | 实时聊天、推送、游戏 |
握手升级
WebSocket 连接通过 HTTP 101 Upgrade 建立:
客户端请求:
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服务端响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
握手成功后,HTTP 连接升级为 WebSocket,不再遵循 HTTP 协议。
消息帧结构
WebSocket 数据以帧(Frame)为单位传输:
- opcode:0x1 文本帧、0x2 二进制帧、0x8 关闭、0x9 Ping、0xA Pong
- Masking:客户端→服务端的帧必须掩码处理,服务端→客户端不需要
- 帧头极小(2~10 字节),相比 HTTP 头部开销大幅降低
前端使用
const ws = new WebSocket('wss://example.com/ws')
ws.onopen = () => {
ws.send(JSON.stringify({ type: 'join', room: 'general' }))
}
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
console.log('收到消息:', data)
}
ws.onclose = (event) => {
console.log('连接关闭', event.code, event.reason)
}
ws.onerror = (error) => {
console.error('连接错误', error)
}
// 主动关闭
ws.close(1000, '正常关闭')Spring Boot 集成
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ChatHandler(), "/ws/chat")
.setAllowedOrigins("*");
}
}
@Component
public class ChatHandler extends TextWebSocketHandler {
private final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
for (WebSocketSession s : sessions) {
if (s.isOpen()) {
s.sendMessage(message);
}
}
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
}
}心跳与重连
// 客户端心跳保活
let heartbeat = setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }))
}
}, 30000)
// 断线自动重连
ws.onclose = () => {
clearInterval(heartbeat)
setTimeout(() => connect(), 3000)
}适用场景
| 场景 | 说明 |
|---|---|
| 实时聊天 | 消息双向推送,低延迟 |
| 在线协作 | 文档多人同时编辑,操作实时同步 |
| 股票行情 | 服务端主动推送价格变动 |
| 在线游戏 | 高频状态同步 |
| 消息通知 | 替代轮询,服务端事件触发推送 |
关闭状态码
ws.close(1000, '正常关闭')| 状态码 | 含义 |
|---|---|
| 1000 | 正常关闭 |
| 1001 | 端点离开(页面跳转/服务关闭) |
| 1006 | 连接异常断开(无 Close 帧,网络故障) |
| 1008 | 违反策略(消息不合法) |
| 1011 | 服务端内部错误 |
| 4000-4999 | 应用自定义关闭码 |
安全性
Origin 验证:握手请求携带 Origin 头,服务端必须校验,防止跨站 WebSocket 劫持(CSWSH):
// Spring:只允许指定来源
registry.addHandler(handler, "/ws")
.setAllowedOrigins("https://my-frontend.com");使用 wss(WebSocket Secure):WebSocket 同样有明文(ws://)与加密(wss://)两种,生产环境必须使用 wss,底层走 TLS。
消息验证:WebSocket 建立后不自动携带 Cookie 到每帧,需在握手阶段或首条消息中完成身份认证(Token 传入 URL 参数或首帧 payload)。
子协议(Sec-WebSocket-Protocol)
握手阶段可以协商应用层子协议,常用于统一消息格式:
客户端:
Sec-WebSocket-Protocol: chat, v2.chat
服务端(选择其一):
Sec-WebSocket-Protocol: v2.chat
STOMP over WebSocket
STOMP(Simple Text Oriented Messaging Protocol)是常见的 WebSocket 上层协议,Spring WebSocket 内置支持:
// 前端 @stomp/stompjs
const client = new Client({
brokerURL: 'wss://example.com/ws',
onConnect: () => {
// 订阅频道
client.subscribe('/topic/general', (message) => {
console.log(JSON.parse(message.body))
})
// 发送消息
client.publish({
destination: '/app/chat',
body: JSON.stringify({ content: 'Hello' }),
})
},
})
client.activate()// Spring Boot 服务端
@Configuration
@EnableWebSocketMessageBroker
public class StompConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic", "/queue");
registry.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS(); // 降级兼容
}
}
@Controller
public class ChatController {
@MessageMapping("/chat")
@SendTo("/topic/general")
public ChatMessage handle(ChatMessage message) {
return message;
}
}STOMP vs 原生 WebSocket:STOMP 提供发布/订阅模型、消息路由、Ack 机制;原生 WebSocket 更轻量,适合自定义协议。
负载均衡与扩展性
WebSocket 是持久连接,负载均衡需要特殊处理:
粘性会话(Sticky Session):同一客户端的请求必须路由到同一服务节点,否则连接断开。Nginx 配置示例:
upstream ws_backend {
ip_hash; # 基于客户端 IP 固定路由
server 10.0.0.1:8080;
server 10.0.0.2:8080;
}消息总线方案:多节点间通过 Redis Pub/Sub 或 MQ 广播消息,每节点只管理自己的连接:
客户端 A → 节点 1 → Redis Pub/Sub → 节点 2 → 客户端 B
Spring WebSocket + Spring Session + Redis 是常见生产方案。
WebSocket vs SSE vs 轮询 选型
| 方案 | 方向 | 连接 | 复杂度 | 适用场景 |
|---|---|---|---|---|
| 短轮询 | 单向(拉) | 频繁建立 | 低 | 低频通知,兼容旧系统 |
| 长轮询 | 单向(拉) | 保持 hold | 中 | 无 SSE/WS 支持的场景 |
| SSE | 单向(服务端推) | 持久 HTTP | 低 | 消息流、进度推送、AI 流式输出 |
| WebSocket | 双向 | 持久 TCP | 中 | 聊天、协同编辑、游戏、高频双向通信 |
SSE(Server-Sent Events) 适合只需要服务端推送的场景,基于普通 HTTP,自动重连,无需额外握手协议:
const es = new EventSource('/api/stream')
es.onmessage = (e) => console.log(e.data)
es.addEventListener('progress', (e) => updateProgress(e.data))