一、WebSocket 是什么?
WebSocket 是一种全双工通信协议,建立在 TCP 之上,允许客户端与服务端之间进行持久化、低延迟的双向数据传输。
- 传统 HTTP:请求-响应模式,每次通信都需要新建连接。
- WebSocket:一次握手建立连接后,双方可随时主动发送消息,直到连接关闭。
协议标识:
ws://(非加密)或wss://(加密,类似 HTTPS)

二、WebSocket 与 HTTP 的关系
- WebSocket 握手阶段使用 HTTP 协议(HTTP/1.1 Upgrade 请求)。一次握手,浏览器请求将http协议升级为websocket协议
- 握手成功后,切换为 WebSocket 帧协议,不再使用 HTTP。
- 因此,WebSocket 可以复用 HTTP 的端口(如 80、443)和部分上下文(如 Cookie、Session)。
三、Java 中使用 WebSocket(基于 JSR-356 标准)
1. 添加依赖(Spring Boot)
XML
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
2. 启用 WebSocket 支持
java
@Configuration
@EnableWebSocket
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
注意:若使用原生
@ServerEndpoint(非 Spring WebSocket),需此配置;若使用@MessageMapping则不需要。
四、核心注解与生命周期方法
1. @ServerEndpoint
标记一个类为 WebSocket 端点。

java
@ServerEndpoint("/chat")
@Component
public class ChatEndpoint { ... }
value:访问路径(如ws://localhost:8080/chat)- 可配合
configurator实现自定义握手逻辑

2. 生命周期回调方法
| 注解 | 触发时机 | 参数说明 |
|---|---|---|
@OnOpen |
连接建立成功 | Session session, EndpointConfig config |
@OnMessage |
收到客户端消息 | String message 或 byte[] data |
@OnClose |
连接关闭 | Session session, CloseReason reason |
@OnError |
发生异常 | Session session, Throwable error |
每个连接对应一个
Session实例,服务端可通过它向特定客户端发送消息。

3、前端发送websocket请求

五、Session 对象
javax.websocket.Session:代表一个 WebSocket 连接。- 常用方法:
getBasicRemote().sendText(String):同步发送文本消息getAsyncRemote().sendText(String):异步发送(不阻塞线程)close():主动关闭连接
⚠️ 不要将
Session存入静态集合时未考虑线程安全或连接失效问题。
六、共享 HttpSession(关键!)
WebSocket 握手是新的 HTTP 请求,默认不会自动复用登录时的 HttpSession。需通过 Configurator 显式传递。
1. 自定义 Configurator
java
public class HttpSessionConfig extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response) {
HttpSession httpSession = (HttpSession) request.getHttpSession();
if (httpSession != null) {
config.getUserProperties().put(HttpSession.class.getName(), httpSession);
}
}
}
2. 在端点中使用
java
@ServerEndpoint(value = "/chat", configurator = HttpSessionConfig.class)
public class ChatEndpoint {
private HttpSession httpSession;
@OnOpen
public void onOpen(Session session, EndpointConfig config) {
this.httpSession = (HttpSession) config.getUserProperties()
.get(HttpSession.class.getName());
// 此时可获取登录用户
String user = (String) httpSession.getAttribute("user");
}
}
✅ 关键点:前后端必须同域 ,浏览器才会在 WebSocket 握手时自动携带
JSESSIONIDCookie。
七、消息格式设计
WebSocket 协议不限制消息内容格式,但建议:
-
使用 JSON 作为消息载体(结构清晰、跨语言)
-
消息体包含元数据,例如:
{
"from": "alice",
"to": "bob",
"type": "private", // 或 "broadcast"
"content": "Hello!",
"timestamp": 1732000000
}
服务端根据 type 和 to 字段决定转发逻辑。
八、实现群聊与私聊
1. 维护在线用户映射
java
private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
- Key:用户名(或其他唯一标识)
- Value:对应的
Session
2. 广播消息
java
public void broadcastAllUsers(String message) {
for (Session session : onlineUsers.values()) {
try {
session.getBasicRemote().sendText(message);
} catch (IOException e) {
// 处理异常(如连接已断开)
}
}
}
3. 私聊消息
java
@OnMessage
public void onMessage(String jsonMessage) {
Message msg = JSON.parseObject(jsonMessage, Message.class);
Session target = onlineUsers.get(msg.getToName());
if (target != null && target.isOpen()) {
String reply = buildPrivateMessage(msg);
target.getBasicRemote().sendText(reply);
}
}
⚠️ 发送前务必检查
session.isOpen(),避免向已关闭连接写数据。
九、连接管理与异常处理
1. 用户上线/下线
@OnOpen:将用户加入onlineUsers@OnClose:从onlineUsers移除用户,并通知其他人
2. 异常处理
java
@OnError
public void onError(Session session, Throwable error) {
// 记录日志
// 可尝试关闭 session
try {
session.close();
} catch (IOException ignored) {}
}
3. 清理无效连接
定期扫描 onlineUsers,移除已关闭的 Session(可选,适用于高并发场景)。
十、安全注意事项
- 身份验证 :不要信任 WebSocket 消息中的
from字段,应通过HttpSession获取真实用户。 - 输入校验:对收到的消息内容做合法性检查,防止 XSS 或注入攻击。
- 连接限制:防止单个 IP 创建过多连接(可结合 Spring Security 或自定义拦截器)。
- 敏感信息:避免在消息中明文传输密码等敏感数据。
十一、总结:WebSocket 开发要点
| 模块 | 关键点 |
|---|---|
| 连接建立 | 使用 @ServerEndpoint + Configurator 共享 HttpSession |
| 用户识别 | 从 HttpSession 获取登录用户,不要依赖消息体伪造身份 |
| 消息路由 | 通过 JSON 消息头区分广播/私聊,维护 user → session 映射 |
| 连接管理 | 上线加入集合,下线移除,并广播状态变更 |
| 异常处理 | 捕获发送异常,清理无效连接,避免内存泄漏 |
| 安全校验 | 验证用户身份、消息内容合法性 |
| 调试支持 | 利用浏览器 DevTools 和服务端日志 |
十二、URI 模板 + @PathParam
问题:客户端发送的链接是:ws://localhost:8080/ws/sid,后面的这个sid是随机生成的,服务端用@ServerEndPoint("/ws/{sid}")接,能接到吗?
答案:能接到,前提是使用支持路径参数的 WebSocket 容器(如 Tomcat 8.0+ / Jetty 9.1+)并正确配置。
Java 的 JSR-356 标准 本身不原生支持路径参数(如 /ws/{sid}) ,但主流服务器实现(如 Tomcat、Jetty)扩展支持了 URI 模板(URI Template)语法 ,允许在 @ServerEndpoint 中使用 {param} 形式的路径变量。
12-1、服务端写法(以 Tomcat 为例)
java
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/ws/{sid}")
@Component
public class MyWebSocket {
@OnOpen
public void onOpen(Session session,
@PathParam("sid") String sid) {
System.out.println("连接建立,sid = " + sid);
// 可将 sid 存入 session 属性或关联业务逻辑
}
@OnMessage
public void onMessage(String message,
@PathParam("sid") String sid) {
System.out.println("收到消息,sid = " + sid + ", 内容: " + message);
}
}
🔸 关键注解:
@PathParam("sid")------ 用于注入路径中的sid值。
12-2、客户端连接方式
javascript
// sid 是任意字符串,例如 UUID、时间戳等
const sid = "a1b2c3d4";
const socket = new WebSocket(`ws://localhost:8080/ws/${sid}`);
只要服务端部署成功,该连接就能被 @ServerEndpoint("/ws/{sid}") 捕获,并将 sid 的值传入方法参数。
12-3、注意事项
1. 服务器必须支持 URI 模板
- ✅ Tomcat 7.0.47+ / 8.0+ :支持
{param} - ✅ Jetty 9.1+:支持
- ❌ 某些旧版或轻量容器:可能不支持,会报 404
Spring Boot 内嵌 Tomcat 默认支持,无需额外配置。
2. 路径参数只能用于路径段,不能用于查询参数
- ✅ 正确:
/ws/{sid}→ws://host/ws/abc123 - ❌ 无效:
/ws?sid={sid}→ 查询参数需通过其他方式获取(见下文)
3. 如何获取查询参数(如 ?token=xxx)?
如果客户端用:
java
new WebSocket("ws://localhost:8080/ws/sid123?token=abc");
则需在 Configurator 中解析:
java
public class CustomConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request,
HandshakeResponse response) {
// 获取查询参数
Map<String, List<String>> params = request.getParameterMap();
List<String> tokens = params.get("token");
String token = tokens != null && !tokens.isEmpty() ? tokens.get(0) : null;
config.getUserProperties().put("token", token);
}
}
然后在 @OnOpen 中:
java
String token = (String) config.getUserProperties().get("token");
12-4、完整示例:带 sid 和 token 的 WebSocket
服务端
java
@ServerEndpoint(
value = "/ws/{sid}",
configurator = CustomConfigurator.class
)
@Component
public class ChatEndpoint {
@OnOpen
public void onOpen(Session session,
EndpointConfig config,
@PathParam("sid") String sid) {
String token = (String) config.getUserProperties().get("token");
System.out.println("sid: " + sid + ", token: " + token);
// 验证 token,绑定 sid 到用户等
}
}
客户端
javascript
const sid = "user123";
const token = "abcxyz";
const ws = new WebSocket(`ws://localhost:8080/ws/${sid}?token=${token}`);
12-5、常见错误排查
| 现象 | 原因 | 解决 |
|---|---|---|
| 连接 404 | 路径不匹配或容器不支持 {} |
检查 Tomcat 版本,确认路径拼写 |
@PathParam 为 null |
注解未正确使用或参数名不一致 | 确保 @PathParam("sid") 与 {sid} 名称一致 |
| 无法获取查询参数 | 未使用 Configurator |
实现自定义 Configurator 解析 request.getParameterMap() |
12-6、总结
| 问题 | 答案 |
|---|---|
ws://localhost:8080/ws/sid 能否被 @ServerEndpoint("/ws/{sid}") 接收? |
✅ 可以 |
| 需要什么条件? | 使用支持 URI 模板的服务器(如 Tomcat 8+),并用 @PathParam("sid") 注入 |
| 能否同时用路径参数和查询参数? | ✅ 路径参数用 @PathParam,查询参数用 Configurator + getParameterMap() |