沉梦听雨的编程指南 沉梦听雨的编程指南
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • 网络通信
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 多媒体框架
  • 虚拟机
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Axios入门
  • Vue Router入门
  • Pinia入门
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)

沉梦听雨

时间是最好的浸渍剂,而沉淀是最好的提纯器🚀
首页
  • 基础篇
  • 集合篇
  • 并发篇
  • JVM
  • 新特性
  • 计算机网络
  • 操作系统
  • 数据结构与算法
  • 基础篇
  • MySql
  • Redis
  • 达梦数据库
  • Spring
  • SpringBoot
  • Mybatis
  • Shiro
  • 设计须知
  • UML画图
  • 权限校验
  • 设计模式
  • API网关
  • 网络通信
  • 消息队列
  • SpringCloud
  • 分布式事务
  • 云存储
  • 搜索引擎
  • 多媒体框架
  • 虚拟机
  • 开发工具篇
  • 工具库篇
  • 开发技巧篇
  • 工具类系列
  • 随笔
  • 前端环境搭建
  • HTML与CSS
  • JS学习
  • Axios入门
  • Vue Router入门
  • Pinia入门
  • Vue3入门
  • Vue3进阶
  • 黑马Vue3
  • 脚手架搭建
  • 瑞吉外卖
  • 黑马点评
  • vue-blog
  • 沉梦接口开放平台
  • 用户中心
  • 聚合搜索平台
  • 仿12306项目
  • 壁纸小程序项目
  • RuoYi-Vue
  • 博客搭建
  • 网站收藏箱
  • 断墨寻径摘录
  • 费曼学习法
Github (opens new window)
  • API网关

  • 网络通信

    • 讲讲五种通信方式的区别
    • Dubbo入门
    • gRPC入门
    • Netty入门
    • WebSocket入门
      • WebSocket 简介
      • 为什么需要 WebSocket?
      • WebSocket 工作原理
      • 常见的 WebSocket 开发方式
      • WebSocketHandler 接口解析
      • 处理器写法对比分析
      • Spring Boot 实现 WebSocket 示例
        • 引入依赖
        • 创建 WebSocket 配置
        • 编写 WebSocket 处理器
        • 继承 Handler 写法(推荐)
        • 注解式写法
        • 实现处理器接口写法
      • 测试 WebSocket
      • 常见问题与优化
      • 学习参考
  • 消息队列

  • Spring Cloud

  • 分布式事务

  • 云存储

  • 搜索引擎

  • 多媒体框架

  • 虚拟机

  • 微服务
  • 网络通信
沉梦听雨
2025-08-11
目录

WebSocket入门

# WebSocket 入门

# WebSocket 简介

WebSocket 是一种在单个 TCP 连接上进行 全双工通信 的网络协议。它最早由 HTML5 标准引入,旨在解决 HTTP 协议在实时双向通信方面的不足。

  • HTTP 协议有一个缺陷:通信只能由客户端发起。

核心特点:

  1. 全双工:客户端和服务器都可以主动发送消息,而不必等待对方请求。
  2. 持久连接:连接建立后不会像 HTTP 一样每次请求都要重新建立 TCP 连接。
  3. 低延迟:省去了频繁的 HTTP 请求头开销,消息推送更及时。
  4. 轻量化数据帧:相比 HTTP 请求动辄几百字节的头部,WebSocket 帧头只有 2~14 字节,更适合高频消息传输。

典型应用场景:

  • 在线聊天与社交(IM、聊天室、弹幕)
  • 实时数据推送(股票行情、体育比分)
  • 协同办公(在线文档、白板)
  • 在线游戏(状态同步、实时对战)
  • 语音视频通话(信令传输)

协议结构简述:

  • 运行在 TCP 之上
  • 握手过程依赖一次 HTTP 请求(Upgrade 机制)
  • 之后使用自定义的帧格式进行数据传输
  • 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL

与 HTTP 的关系可以简单理解为:

  • HTTP:一次性请求-响应
  • WebSocket:建立持久通道,实时双向通信

# 为什么需要 WebSocket?

在传统 Web 应用中,客户端和服务器之间主要通过 HTTP 请求-响应 模型通信。这种模式存在一个明显缺点:

  • 单向性:必须由客户端发起请求,服务器才能响应。
  • 实时性不足:需要轮询(Polling)或长轮询(Long Polling)才能接近实时,但浪费带宽与资源。

WebSocket 解决了这一问题——它建立在 TCP 之上,实现了 全双工通信,允许服务器主动向客户端推送消息,非常适合 实时聊天、在线协作、股票行情、游戏对战 等场景。

# WebSocket 工作原理

WebSocket 的连接建立分两步:

  1. HTTP 握手:客户端发送一个带有 Upgrade: websocket 头的请求,服务器确认后升级协议。
  2. 全双工通信:握手成功后,客户端和服务器通过同一个 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=
1
2
3
4
5
6
7
8
9
10
11
12
13

# 常见的 WebSocket 开发方式

在 Java 生态中,WebSocket 常用的开发方式有:

  1. Java EE 标准 API (javax.websocket / jakarta.websocket)
  2. Spring WebSocket(基于 Spring 框架的更易用封装)
  3. Netty WebSocket(适合高并发、定制化场景)

# WebSocketHandler 接口解析

  1. afterConnectionEstablished:连接成功后调用。
  2. handleMessage:处理发送来的消息。
  3. handleTransportError:连接异常时调用。
  4. afterConnectionClosed:连接关闭后调用。
  5. 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();
}
1
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>
1
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("*");
    }
}
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

# 编写 WebSocket 处理器

有以下几种写法:

  1. 继承 TextWebSocketHandler / BinaryWebSocketHandler(Handler 写法)
  2. 使用 @ServerEndpoint(注解写法)
  3. 实现 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);
    }
}
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
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);
    }
}
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
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)。

相关问题解释

  1. private static final Set<ChatEndpoint> CLIENTS = new CopyOnWriteArraySet<>();

    • 作用:

      • 保存当前所有已连接的客户端实例(每个客户端连接对应一个 ChatEndpoint 对象)。

      • 在广播消息时,遍历这个集合,向每个连接发送数据。

    • 为什么是 static?

      • 每个客户端连接进来时,@ServerEndpoint 会创建一个新的 ChatEndpoint 实例。

      • 如果不加 static,每个实例都有自己独立的集合,互相看不到对方的连接,广播就无法实现。

      • static 保证集合是类级别的共享变量,所有实例都能访问同一份连接列表。

    • 为什么用 CopyOnWriteArraySet?

      • 线程安全:WebSocket 是多线程的,多个连接可能同时增删,普通集合会线程不安全。

      • CopyOnWrite 是读多写少场景的高效选择(大部分时间是广播消息 = 读操作)。

  2. private Session session;

  • 作用:

    • 保存当前连接的 javax.websocket.Session 对象,它是与客户端通信的通道。

    • 通过 session.getBasicRemote().sendText(...) 向这个客户端发送消息。

    • 每个 ChatEndpoint 实例只对应一个 session,表示它代表的那个客户端的连接。

  1. private String username;

    • 作用:

      • 保存当前连接的用户名(或用户ID),方便日志打印、消息标记、业务逻辑判断(比如权限校验、单发消息)。

      • 来自 @PathParam 或连接参数。

数据流示意

  1. 客户端连接时:
    • 创建一个新的 ChatEndpoint 实例
    • 把它加到 CLIENTS 集合里
    • session 和 username 绑定到这个实例
  2. 收到消息时:
    • 遍历 CLIENTS 集合
    • 用集合里每个实例的 session 发消息给对应的客户端
  3. 连接关闭时:
    • 从 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;
    }
}
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
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 或消息队列同步消息。

# 学习参考

  • 万字详解,带你彻底掌握 WebSocket 用法(至尊典藏版)写的不错_websocket用法-CSDN博客 (opens new window)

  • 使用websocket搭建在线聊天室-黑马 (opens new window)

  • 一文搞懂四种 WebSocket 使用方式_enablewebsocket-CSDN博客 (opens new window)

上次更新: 2025/8/15 16:36:59
Netty入门
SpringBoot事件机制

← Netty入门 SpringBoot事件机制→

Theme by Vdoing | Copyright © 2023-2025 沉梦听雨 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式