1. WebSocket 核心概念与基础原理
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,它解决了传统 HTTP 轮询带来的性能瓶颈问题。与 HTTP 请求-响应模式不同,WebSocket 建立连接后,服务器可以主动向客户端推送数据,特别适合实时性要求高的场景。
1.1 协议握手过程解析
WebSocket 连接始于 HTTP 升级握手:
- 客户端发送包含
Upgrade: websocket和Sec-WebSocket-Key的特殊 HTTP 请求 - 服务端返回
101 Switching Protocols响应 - 连接升级成功后,通信双方通过数据帧(frame)传输数据
关键点在于:
- 握手阶段使用 HTTP 协议,后续通信使用 WebSocket 二进制帧
- 默认端口与 HTTP 相同(80/443),但可使用任意端口
- 帧类型包含文本、二进制、ping/pong 和控制帧等
1.2 与 HTTP 轮询的对比优势
传统方案如长轮询(Long Polling)存在明显缺陷:
- 每次请求都需要完整的 HTTP 头(通常 800+ 字节)
- 高延迟(需要等待请求往返)
- 服务器推送需要客户端先发起请求
WebSocket 的优势体现在:
- 首次握手后,数据传输只需 2 字节的帧头
- 双向实时通信,服务器可主动推送
- 单个连接可复用,减少 TCP 连接开销
实测案例:某股票行情系统改用 WebSocket 后,带宽消耗降低 87%,延迟从 2s 降至 50ms 以内
2. 前端 WebSocket API 实战指南
2.1 基础连接建立
现代浏览器均内置 WebSocket API,基础使用如下:
javascript复制// 创建连接(ws:// 或 wss://)
const socket = new WebSocket('wss://api.example.com/realtime')
// 事件监听
socket.onopen = () => console.log('连接已建立')
socket.onclose = (event) => {
console.log(`连接关闭,代码: ${event.code}, 原因: ${event.reason}`)
// 自动重连逻辑应在此处实现
}
// 错误处理(重要!)
socket.onerror = (error) => {
console.error('WebSocket 错误:', error)
// 实际项目中应包含重试机制和降级方案
}
2.2 消息收发最佳实践
消息处理核心要点:
javascript复制// 接收消息
socket.onmessage = (event) => {
try {
const data = JSON.parse(event.data)
// 处理业务数据
} catch (e) {
console.error('消息解析错误', e)
}
}
// 发送消息(注意序列化)
function sendMessage(type, payload) {
if (socket.readyState === WebSocket.OPEN) {
socket.send(JSON.stringify({ type, payload }))
} else {
console.warn('连接未就绪,当前状态:', socket.readyState)
// 应实现消息队列暂存
}
}
2.3 连接状态管理
WebSocket 有四种就绪状态:
- CONNECTING (0):连接中
- OPEN (1):已连接
- CLOSING (2):关闭中
- CLOSED (3):已关闭
关键技巧:
- 在发送消息前必须检查
readyState - 关闭连接时应指定 code 和 reason:
javascript复制socket.close(1000, '用户主动断开') // 1000=正常关闭 - 实现自动重连机制(带指数退避)
3. Spring Boot 服务端实现详解
3.1 基础服务端配置
Spring Boot 通过 spring-boot-starter-websocket 提供支持:
java复制@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/realtime")
.setAllowedOrigins("*") // 生产环境应严格限制
.addInterceptors(new HttpSessionHandshakeInterceptor());
}
@Bean
public WebSocketHandler myHandler() {
return new MyWebSocketHandler();
}
}
3.2 消息处理核心逻辑
自定义 Handler 示例:
java复制public class MyWebSocketHandler extends TextWebSocketHandler {
private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
@Override
public void afterConnectionEstablished(WebSocketSession session) {
String userId = extractUserId(session);
sessions.put(userId, session);
}
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
// 处理客户端消息
String payload = message.getPayload();
// 业务逻辑处理...
// 响应客户端
session.sendMessage(new TextMessage("已收到: " + payload));
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
String userId = extractUserId(session);
sessions.remove(userId);
}
}
3.3 集群解决方案
单机版 WebSocket 无法满足分布式需求,常见集群方案:
-
广播方案:
- 使用 Redis Pub/Sub 广播消息
- 实现简单但存在资源浪费
-
路由方案:
- 采用 STOMP over WebSocket
- 结合 RabbitMQ 等消息队列
- 示例配置:
java复制@Configuration @EnableWebSocketMessageBroker public class WebSocketBrokerConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableStompBrokerRelay("/topic") .setRelayHost("rabbitmq-host") .setRelayPort(61613); config.setApplicationDestinationPrefixes("/app"); } }
4. 生产环境关键问题处理
4.1 安全防护要点
-
WSS 必须启用:
- Chrome 会标记非安全连接为 "insecure WebSocket connection"
- 使用 Let's Encrypt 免费证书
-
Origin 校验:
java复制// Spring 拦截器示例 public boolean validateOrigin(HttpServletRequest request) { String origin = request.getHeader("Origin"); return allowedOrigins.contains(origin); } -
消息大小限制:
properties复制# application.properties spring.websocket.max-text-message-size=65536 spring.websocket.max-binary-message-size=65536
4.2 连接稳定性保障
常见异常处理:
-
Stream disconnected before completion:
- 通常因心跳超时导致
- 解决方案:实现 ping/pong 机制
javascript复制// 前端心跳 setInterval(() => { if (socket.readyState === WebSocket.OPEN) { socket.send('ping'); } }, 30000);
-
WebSocket closed by server:
- 检查服务端资源限制(如最大连接数)
- 监控连接泄漏(未正确关闭的连接)
4.3 性能优化技巧
-
二进制传输:
javascript复制// 前端发送二进制 const buffer = new ArrayBuffer(16); socket.send(buffer); // Spring 接收 @Override protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) { ByteBuffer payload = message.getPayload(); // 处理二进制数据... } -
连接复用:
- 同一页面避免创建多个 WebSocket 连接
- 使用共享连接+多路复用技术
-
监控指标:
- 活跃连接数
- 消息吞吐量
- 平均延迟
5. 高级应用场景拓展
5.1 结合 Protobuf 提升效率
-
定义 proto 文件:
protobuf复制syntax = "proto3"; message StockQuote { string symbol = 1; double price = 2; int64 timestamp = 3; } -
前后端统一使用二进制传输:
javascript复制// 前端发送 const quote = StockQuote.create({symbol: 'AAPL', price: 175.32}); socket.send(StockQuote.encode(quote).finish());
5.2 移动端适配要点
-
后台保活:
- iOS 需配置 VoIP 背景模式
- Android 使用 Foreground Service
-
网络切换处理:
java复制// Android 示例 private val networkCallback = object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) { reconnectWebSocket() } }
5.3 压力测试方案
使用工具模拟大规模连接:
bash复制# 使用 websocket-bench
wsbench -c 5000 -r 100 -u wss://example.com/realtime
关键指标监控:
- 内存消耗(约 10KB/连接)
- CPU 使用率
- 网络带宽
我在实际项目中发现,当连接数超过 1 万时,选择合适的 WebSocket 服务器实现(如 Netty)比 Tomcat 有显著性能优势,内存占用可降低 40% 以上。另外,对于突发性消息风暴,采用批处理发送策略能有效降低系统负载。
