什么是 WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它解决了传统 HTTP 协议”只能由客户端发起请求”的局限,让服务器可以主动向客户端推送数据。
简单来说:HTTP 是”你问我答”,WebSocket 是”随时都能聊”。
核心特性
1. 双向通信
HTTP 请求只能由客户端发起,服务器无法主动推送。WebSocket 建立连接后,双方可以随时互发消息,非常适合聊天、协同编辑等场景。
2. 实时性强
WebSocket 连接是持久的,数据一旦产生就可以立即发送,无需像 HTTP 那样每次重新建立连接,延迟极低。
3. 开销小
HTTP 每次请求都要携带完整的 Header(Cookie、User-Agent 等),而 WebSocket 在连接建立后,数据帧的头部最小只需 2 字节,非常适合高频、小数据量的场景。
工作原理
握手阶段(HTTP Upgrade)
WebSocket 连接始于一次特殊的 HTTP 请求:
1 2 3 4 5 6
| GET /ws HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13
|
服务器同意后返回:
1 2 3 4
| HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
|
101 Switching Protocols 表示协议切换成功,之后的通信就走 WebSocket 协议了。
数据传输阶段
握手成功后,TCP 连接保持打开,双方通过数据帧(Frame) 交换消息。WebSocket 支持文本和二进制两种数据类型。
连接关闭阶段
任意一方都可以发送关闭帧来优雅终止连接,另一方回复确认后,TCP 连接关闭。
WebSocket vs HTTP
| 特性 |
WebSocket |
HTTP |
| 通信方式 |
全双工,双方随时互发 |
半双工,客户端请求-服务端响应 |
| 连接模型 |
持久连接(一次握手,长期复用) |
短连接(请求-响应后关闭)或 HTTP/2 多路复用 |
| 数据开销 |
连接建立后头部最小 2 字节 |
每次请求/响应都携带完整 Header |
| 实时性 |
原生支持,服务端可主动推送 |
需借助轮询或 SSE 模拟 |
| 典型场景 |
聊天、游戏、行情推送、协同编辑 |
网页浏览、REST API、表单提交 |
| 协议标识 |
ws://(明文)/ wss://(加密) |
http:// / https:// |
在 Spring Boot 中的应用
下面演示如何在 Spring Boot 项目中集成 WebSocket,实现一个简单的消息广播功能。
1. 引入依赖
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency>
|
2. 配置类
启用 WebSocket 支持,并注册处理器和拦截器:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {
@Autowired private WebSocketDemoHandler webSocketDemoHandler;
@Autowired private WebSocketDemoInterceptor webSocketDemoInterceptor;
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketDemoHandler, "/ws") .addInterceptors(webSocketDemoInterceptor) .setAllowedOrigins("*"); } }
|
3. 拦截器
拦截器在握手前后执行,可用于鉴权、参数提取等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| @Component public class WebSocketDemoInterceptor implements HandshakeInterceptor {
@Override public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) { String username = request.getURI().getQuery(); if (username == null || username.isBlank()) { return false; } attributes.put("username", username); return true; }
@Override public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception exception) { } }
|
4. 消息处理器
核心业务逻辑在这里,负责连接管理、消息收发和广播:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| @Slf4j @Component public class WebSocketDemoHandler extends TextWebSocketHandler {
private static final Map<String, WebSocketSession> SESSION_MAP = new ConcurrentHashMap<>();
@Override public void afterConnectionEstablished(WebSocketSession session) { String username = (String) session.getAttributes().get("username"); SESSION_MAP.put(username, session); log.info("用户 [{}] 连接成功,当前在线人数:{}", username, SESSION_MAP.size()); }
@Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { String payload = message.getPayload(); log.info("收到消息:{}", payload);
for (WebSocketSession s : SESSION_MAP.values()) { if (s.isOpen()) { s.sendMessage(new TextMessage(payload)); } } }
@Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { String username = (String) session.getAttributes().get("username"); SESSION_MAP.remove(username); log.info("用户 [{}] 已断开连接,当前在线人数:{}", username, SESSION_MAP.size()); } }
|
5. 简单前端测试
用浏览器控制台快速测试连接:
1 2 3 4 5 6 7 8
| const ws = new WebSocket('ws://localhost:8080/ws?username=tom');
ws.onopen = () => console.log('连接成功'); ws.onmessage = (e) => console.log('收到消息:', e.data); ws.onclose = () => console.log('连接已关闭');
ws.send('大家好!');
|
生产环境注意事项
- 跨域限制:
setAllowedOrigins("*") 仅用于开发环境,生产环境应指定具体域名。
- 心跳检测:长时间无数据传输时,连接可能被中间代理(Nginx、负载均衡器)断开。建议实现 Ping/Pong 心跳机制。
- 集群部署:多实例部署时,
SESSION_MAP 无法跨节点共享。可借助 Redis Pub/Sub 或消息队列(如 RocketMQ)实现消息广播。
- 连接数上限:单机 WebSocket 连接数受文件描述符限制,需根据业务量调整系统参数。
- 安全加固:生产环境使用
wss:// 加密传输,并在拦截器中做好鉴权。
总结
- WebSocket 通过一次 HTTP 握手建立持久连接,之后实现全双工、低开销的实时通信。
- Spring Boot 通过
spring-boot-starter-websocket 可以快速集成,核心是实现 WebSocketHandler 和可选的 HandshakeInterceptor。
- 生产环境需要关注心跳保活、集群同步和安全防护等问题。
参考资料: