SpringBoot21-WebSocket 完整技术笔记

一、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 messagebyte[] 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 握手时自动携带 JSESSIONID Cookie。


七、消息格式设计

WebSocket 协议不限制消息内容格式,但建议:

  • 使用 JSON 作为消息载体(结构清晰、跨语言)

  • 消息体包含元数据,例如:

    {
    "from": "alice",
    "to": "bob",
    "type": "private", // 或 "broadcast"
    "content": "Hello!",
    "timestamp": 1732000000
    }

服务端根据 typeto 字段决定转发逻辑。


八、实现群聊与私聊

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(可选,适用于高并发场景)。


十、安全注意事项

  1. 身份验证 :不要信任 WebSocket 消息中的 from 字段,应通过 HttpSession 获取真实用户。
  2. 输入校验:对收到的消息内容做合法性检查,防止 XSS 或注入攻击。
  3. 连接限制:防止单个 IP 创建过多连接(可结合 Spring Security 或自定义拦截器)。
  4. 敏感信息:避免在消息中明文传输密码等敏感数据。

十一、总结: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()
相关推荐
赖small强1 天前
【ZeroRange WebRTC】Amazon Kinesis Video Streams WebRTC initSignaling() 技术深度解析
websocket·webrtc·stun·kinesis·initsignaling
终端行者1 天前
Nginx 配置Websocket代理 Nginx 代理 Websocket
运维·websocket·nginx
q***51891 天前
Node.js实现WebSocket教程
websocket·网络协议·node.js
程序员小单2 天前
WebSocket 与 Spring Boot 整合实践
spring boot·websocket·网络协议
翻斗花园正门保安小夏2 天前
HTTPS + WSS(WebSockets) 完整请求流程架构说明及本地开启HTTPS
websocket·网络协议·https
阿珊和她的猫3 天前
WebSocket 与轮询:实时通信技术的对比与选择
网络·websocket·网络协议
im_AMBER3 天前
计网 01 WebSocket | MDN
网络·websocket·网络协议
CesareCheung4 天前
JMeter 进行 WebSocket 接口压测
python·websocket·jmeter