什么是 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) {
// 从请求参数或 Header 中提取用户信息
String username = request.getURI().getQuery(); // 示例:ws://host/ws?username=tom
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('大家好!');

生产环境注意事项

  1. 跨域限制setAllowedOrigins("*") 仅用于开发环境,生产环境应指定具体域名。
  2. 心跳检测:长时间无数据传输时,连接可能被中间代理(Nginx、负载均衡器)断开。建议实现 Ping/Pong 心跳机制。
  3. 集群部署:多实例部署时,SESSION_MAP 无法跨节点共享。可借助 Redis Pub/Sub 或消息队列(如 RocketMQ)实现消息广播。
  4. 连接数上限:单机 WebSocket 连接数受文件描述符限制,需根据业务量调整系统参数。
  5. 安全加固:生产环境使用 wss:// 加密传输,并在拦截器中做好鉴权。

总结

  • WebSocket 通过一次 HTTP 握手建立持久连接,之后实现全双工、低开销的实时通信。
  • Spring Boot 通过 spring-boot-starter-websocket 可以快速集成,核心是实现 WebSocketHandler 和可选的 HandshakeInterceptor
  • 生产环境需要关注心跳保活、集群同步和安全防护等问题。

参考资料: