WebSocket入门
# WebSocket 入门
# WebSocket 简介
WebSocket 是一种在单个 TCP 连接上进行 全双工通信 的网络协议。它最早由 HTML5 标准引入,旨在解决 HTTP 协议在实时双向通信方面的不足。
- HTTP 协议有一个缺陷:通信只能由客户端发起。
核心特点:
- 全双工:客户端和服务器都可以主动发送消息,而不必等待对方请求。
- 持久连接:连接建立后不会像 HTTP 一样每次请求都要重新建立 TCP 连接。
- 低延迟:省去了频繁的 HTTP 请求头开销,消息推送更及时。
- 轻量化数据帧:相比 HTTP 请求动辄几百字节的头部,WebSocket 帧头只有 2~14 字节,更适合高频消息传输。
典型应用场景:
- 在线聊天与社交(IM、聊天室、弹幕)
- 实时数据推送(股票行情、体育比分)
- 协同办公(在线文档、白板)
- 在线游戏(状态同步、实时对战)
- 语音视频通话(信令传输)
协议结构简述:
- 运行在 TCP 之上
- 握手过程依赖一次 HTTP 请求(Upgrade 机制)
- 之后使用自定义的帧格式进行数据传输
- 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL
与 HTTP 的关系可以简单理解为:
- HTTP:一次性请求-响应
- WebSocket:建立持久通道,实时双向通信
# 为什么需要 WebSocket?
在传统 Web 应用中,客户端和服务器之间主要通过 HTTP 请求-响应 模型通信。这种模式存在一个明显缺点:
- 单向性:必须由客户端发起请求,服务器才能响应。
- 实时性不足:需要轮询(Polling)或长轮询(Long Polling)才能接近实时,但浪费带宽与资源。
WebSocket 解决了这一问题——它建立在 TCP 之上,实现了 全双工通信,允许服务器主动向客户端推送消息,非常适合 实时聊天、在线协作、股票行情、游戏对战 等场景。
# WebSocket 工作原理
WebSocket 的连接建立分两步:
- HTTP 握手:客户端发送一个带有
Upgrade: websocket
头的请求,服务器确认后升级协议。 - 全双工通信:握手成功后,客户端和服务器通过同一个 TCP 连接进行双向数据传输。
简化的握手过程:
客户端 -> 服务器:
GET /chat 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=
2
3
4
5
6
7
8
9
10
11
12
13
# 常见的 WebSocket 开发方式
在 Java 生态中,WebSocket 常用的开发方式有:
- Java EE 标准 API (
javax.websocket
/jakarta.websocket
) - Spring WebSocket(基于 Spring 框架的更易用封装)
- Netty WebSocket(适合高并发、定制化场景)
# WebSocketHandler 接口解析
afterConnectionEstablished
:连接成功后调用。handleMessage
:处理发送来的消息。handleTransportError
:连接异常时调用。afterConnectionClosed
:连接关闭后调用。supportsPartialMessages
:是否支持分片消息。
package org.springframework.web.socket;
public interface WebSocketHandler {
void afterConnectionEstablished(WebSocketSession session) throws Exception;
void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception;
void handleTransportError(WebSocketSession session, Throwable exception) throws Exception;
void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception;
boolean supportsPartialMessages();
}
2
3
4
5
6
7
8
9
10
11
12
13
handleMessage 方法中有一个 WebSocketMessage
参数,这也是一个接口,我们一般不直接使用这个接口而是使用它的实现类,它有以下几个实现类:
BinaryMessage
:二进制消息体TextMessage
:文本消息体PingMessage
: Ping 消息体PongMessage
: Pong 消息体
# 处理器写法对比分析
特性 / 写法 | 继承处理器 (extends TextWebSocketHandler ) | 注解式 (@ServerEndpoint ) | 实现处理器接口 (implements WebSocketHandler ) |
---|---|---|---|
核心入口 | 例如继承 TextWebSocketHandler 并重写方法 | 在类上加 @ServerEndpoint 注解 | 实现 WebSocketHandler 接口的 5 个方法 |
主要方法 | afterConnectionEstablished 、handleTextMessage 、afterConnectionClosed 、handleTransportError | @OnOpen 、@OnMessage 、@OnClose 、@OnError | afterConnectionEstablished 、handleMessage 、handleTransportError 、afterConnectionClosed 、supportsPartialMessages |
框架依赖 | 依赖 Spring WebSocket | 依赖 Java WebSocket API(javax.websocket 或 jakarta.websocket ) | 依赖 Spring WebSocket |
路径注册方式 | 在 WebSocketHandlerRegistry 里配置 | 注解内直接写(@ServerEndpoint("/chat/{user}") ) | 在 WebSocketHandlerRegistry 里配置 |
集成 Spring 容器 | 完全在 Spring 容器管理 | 需要 ServerEndpointExporter (内嵌容器时)+ 手动注入 Spring Bean(需要额外处理) | 完全在 Spring 容器管理 |
代码风格 | Java 风格,面向对象 | 类似 JSR 356 标准 WebSocket API,面向事件 | 接口驱动,较底层 |
易用性 | 中等,方法少且直观 | 最高,注解标注即可运行 | 最低,需要自己处理消息类型转换等细节 |
扩展性 | 中等,可利用 Spring 配置链路(拦截器、消息转换器) | 较弱,需要自己实现广播、消息分发等 | 高,可完全自定义逻辑 |
常见场景 | Spring Boot 内的 IM 聊天、通知推送 | 独立 Java WebSocket 服务、跨框架部署 | 高度定制化的实时通讯 |
优点 | 容易与 Spring 生态集成,管理方便 | 简洁直观,快速上手,少配置 | 最大灵活度,可控制底层细节 |
缺点 | 绑定 Spring,移植到非 Spring 环境麻烦 | 与 Spring 集成需要额外注入支持 | 代码量大、冗长,学习曲线高 |
# Spring Boot 实现 WebSocket 示例
代码仓库地址:chenmeng-test-demos/demo18-websocket at master · cmty256/chenmeng-test-demos (opens new window)
# 引入依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 创建 WebSocket 配置
/**
* websocket 配置类
*
* @author chenmeng
*/
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
/**
* (注解写法)创建一个ServerEndpointExporter对象,这个对象会自动注册使用了@ServerEndpoint注解的类
*/
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
/**
* 注册处理器
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry
// 添加处理器
.addHandler(new ChatTextWebSocketHandler(), "/ws/chat")
.addHandler(new ChatWebSocketHandler(), "/ws/chat2")
// 允许跨域
.setAllowedOrigins("*");
}
}
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
# 编写 WebSocket 处理器
有以下几种写法:
- 继承
TextWebSocketHandler
/BinaryWebSocketHandler
(Handler 写法) - 使用
@ServerEndpoint
(注解写法) - 实现
WebSocketHandler
接口(可以兼容发送多种消息,但是不常用)
# 继承 Handler 写法(推荐)
/**
* WebSocket 处理器(继承 Handler 写法)
* 连接地址:ws://localhost:9118/websocket/ws/chat
*
* @author chenmeng
*/
@Slf4j
public class ChatTextWebSocketHandler extends TextWebSocketHandler {
private static final CopyOnWriteArrayList<WebSocketSession> SESSIONS = new CopyOnWriteArrayList<>();
/**
* 连接建立后调用
*/
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) {
SESSIONS.add(session);
log.info("[ChatWebSocketHandler]连接建立: {}", session.getId());
}
/**
* 处理发送来的消息
*/
@Override
protected void handleTextMessage(@NonNull WebSocketSession session, TextMessage message) throws Exception {
log.info("[ChatWebSocketHandler]收到消息: {}", message.getPayload());
for (WebSocketSession s : SESSIONS) {
s.sendMessage(new TextMessage("Echo: " + message.getPayload()));
}
}
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage<?> message) throws Exception {
super.handleMessage(session, message);
}
/**
* 连接关闭后调用
*/
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session, @NonNull CloseStatus status) {
SESSIONS.remove(session);
log.info("[ChatWebSocketHandler]连接关闭: {}", session.getId());
}
/**
* 连接异常时调用
*/
@Override
public void handleTransportError(@NonNull WebSocketSession session, @NonNull Throwable exception) throws Exception {
log.info("[ChatWebSocketHandler]连接异常: {}", session.getId());
session.close();
SESSIONS.remove(session);
super.handleTransportError(session, exception);
}
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
特点:
- 完全由 Spring WebSocket 模块管理(
WebSocketConfigurer
注册)。 - 支持与 Spring Security、Spring Session 深度整合。
- 更容易做 权限拦截、消息路由、统一异常处理。
- 更适合结合 STOMP 协议和消息代理(RabbitMQ、Kafka)进行大规模实时推送。
- 有全局拦截器(
HandshakeInterceptor
)可用于 token 校验、参数解析。
企业使用场景:
- 高并发(金融行情推送、在线协同编辑、IoT 设备管理)。
- 需要统一的安全、日志、监控。
- 服务端使用 Spring Boot + Spring Cloud 体系。
# 注解式写法
/**
* WebSocket 处理器(注解式写法)
* 连接地址:ws://localhost:9118/websocket/ws/chat/{username}
*
* @author chenmeng
*/
@Component
@Slf4j
@EqualsAndHashCode()
@ServerEndpoint("/ws/chat/{username}")
public class ChatEndpointHandler {
/**
* 保存所有连接的客户端
* CopyOnWrite 是读多写少场景的高效选择(大部分时间是广播消息 = 读操作)
*/
private static final Set<ChatEndpointHandler> CLIENTS = new CopyOnWriteArraySet<>();
/**
* 当前会话
*/
private Session session;
/**
* 用户名
*/
private String username;
@OnOpen
public void onOpen(Session session, @PathParam("username") String username) {
this.session = session;
this.username = username;
CLIENTS.add(this);
log.info("[ChatEndpoint] 连接建立: {} (sessionId={})", username, session.getId());
}
@OnMessage
public void onMessage(String message) {
log.info("[ChatEndpoint] 收到消息: {} -> {}", username, message);
// 广播消息
for (ChatEndpointHandler client : CLIENTS) {
try {
client.session.getBasicRemote()
.sendText("Echo from " + username + ": " + message);
} catch (IOException e) {
log.error("[ChatEndpoint] 发送消息失败", e);
}
}
}
@OnClose
public void onClose() {
CLIENTS.remove(this);
log.info("[ChatEndpoint] 连接关闭: {}", username);
}
@OnError
public void onError(Session session, Throwable error) {
log.error("[ChatEndpoint] 连接异常: {}", username, error);
}
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
特点:
- 来自 Java EE(JSR-356)规范,Tomcat / Jetty / Undertow 等原生支持。
- 结构直观,少配置,类上直接标注即可运行。
- 不依赖 Spring WebSocket 模块,但也能和 Spring Boot 集成(要额外配置)。
- 不如 Handler 写法那样容易集成 Spring Security 或统一管理 session。
企业使用场景:
- 轻量服务(简单的聊天、通知推送)。
- 对 Spring Session、Security 没有强绑定需求。
- 快速原型或 PoC(Proof of Concept)。
相关问题解释
private static final Set<ChatEndpoint> CLIENTS = new CopyOnWriteArraySet<>();
作用:
保存当前所有已连接的客户端实例(每个客户端连接对应一个
ChatEndpoint
对象)。在广播消息时,遍历这个集合,向每个连接发送数据。
为什么是
static
?每个客户端连接进来时,
@ServerEndpoint
会创建一个新的 ChatEndpoint 实例。如果不加
static
,每个实例都有自己独立的集合,互相看不到对方的连接,广播就无法实现。static
保证集合是类级别的共享变量,所有实例都能访问同一份连接列表。
为什么用
CopyOnWriteArraySet
?线程安全:WebSocket 是多线程的,多个连接可能同时增删,普通集合会线程不安全。
CopyOnWrite
是读多写少场景的高效选择(大部分时间是广播消息 = 读操作)。
private Session session;
作用:
保存当前连接的
javax.websocket.Session
对象,它是与客户端通信的通道。通过
session.getBasicRemote().sendText(...)
向这个客户端发送消息。每个
ChatEndpoint
实例只对应一个session
,表示它代表的那个客户端的连接。
private String username;
作用:
保存当前连接的用户名(或用户ID),方便日志打印、消息标记、业务逻辑判断(比如权限校验、单发消息)。
来自
@PathParam
或连接参数。
数据流示意
- 客户端连接时:
- 创建一个新的
ChatEndpoint
实例 - 把它加到
CLIENTS
集合里 session
和username
绑定到这个实例
- 创建一个新的
- 收到消息时:
- 遍历
CLIENTS
集合 - 用集合里每个实例的
session
发消息给对应的客户端
- 遍历
- 连接关闭时:
- 从
CLIENTS
集合里移除该实例
- 从
# 实现处理器接口写法
/**
* WebSocket 处理器(实现接口写法)
* 地址:ws://localhost:9118/websocket/ws/chat2
*
* @author chenmeng
*/
@Slf4j
public class ChatWebSocketHandler implements WebSocketHandler {
/**
* 保存所有已连接的会话
*/
private static final Set<WebSocketSession> SESSIONS = new CopyOnWriteArraySet<>();
@Override
public void afterConnectionEstablished(@NonNull WebSocketSession session) {
SESSIONS.add(session);
log.info("[ChatWebSocketHandler] 连接建立: {}", session.getId());
}
@Override
public void handleMessage(@NonNull WebSocketSession session,
@NonNull WebSocketMessage<?> message) throws IOException {
String payload = message.getPayload().toString();
log.info("[ChatWebSocketHandler] 收到消息: {}", payload);
// 广播消息给所有连接的客户端
for (WebSocketSession s : SESSIONS) {
if (s.isOpen()) {
s.sendMessage(new TextMessage("Echo: " + payload));
}
}
}
@Override
public void handleTransportError(@NonNull WebSocketSession session,
@NonNull Throwable exception) throws IOException {
log.error("[ChatWebSocketHandler] 连接异常: {}", session.getId(), exception);
if (session.isOpen()) {
session.close();
}
SESSIONS.remove(session);
}
@Override
public void afterConnectionClosed(@NonNull WebSocketSession session,
@NonNull CloseStatus closeStatus) {
SESSIONS.remove(session);
log.info("[ChatWebSocketHandler] 连接关闭: {}, 原因: {}", session.getId(), closeStatus);
}
@Override
public boolean supportsPartialMessages() {
// 是否支持分片消息,这里简单返回 false
return false;
}
}
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
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
优点:
- 完全控制消息处理的底层行为。
- 可以同时处理文本、二进制、分片消息等复杂情况。
缺点:
- 必须全部实现,代码量多,容易出错。
- 缺少基类的便利方法,需要自己做类型判断、消息解析等。
# 测试 WebSocket
在线测试工具:WebSocket在线测试工具,websocket接口测试工具 - 在线工具-wetools.com微工具 (opens new window)
# 常见问题与优化
- 心跳检测:防止长时间无数据导致连接断开。
- 消息分组与广播:按房间/频道推送,而不是所有人都收到。
- 安全认证:建立连接前验证用户身份(JWT / Token)。
- 负载均衡:分布式环境中使用 Redis Pub/Sub 或消息队列同步消息。