WebSocket讲解

WebSocket 是一种在单个 TCP 连接上进行"全双工"(Full-Duplex)通信的协议

为了让你更直观地理解,我们可以先用一个生活中的例子来对比 HTTPWebSocket


1. 通俗易懂的类比

HTTP (传统的 Web 通信)
  • 像"发邮件"或"对讲机"

    • 客户端(你)发起请求:"服务器,有新消息吗?"

    • 服务器回复:"没有。"

    • 连接断开。

    • 过了一会儿,你又问:"现在有新消息吗?"

    • 服务器:"有了,给你。"

    • 特点:必须由客户端主动发起,服务器被动回复。每次都要重新建立连接,很麻烦。

WebSocket
  • 像"打电话"

    • 你打给服务器(握手),接通了。

    • 电话线一直通着(长连接)。

    • 你可以说话,服务器也可以随时插嘴告诉你新消息,不需要你一直问。

    • 双方都可以同时说话。

    • 特点:连接一旦建立,双方地位平等,服务器可以主动推送数据给客户端。


2. WebSocket 是什么?(技术定义)

WebSocket 是一种网络传输协议,位于 OSI 模型的应用层。

  • 建立过程 :它借用了 HTTP 协议来"握手"。客户端发送一个特殊的 HTTP 请求(带有 Upgrade: websocket 头),告诉服务器:"我们要不要升级成 WebSocket 协议聊天?"如果服务器同意,连接就会从 HTTP 升级为 WebSocket。

  • 持久化:握手成功后,TCP 连接不会关闭,而是保持打开状态。

  • 协议标识 :HTTP 是 http://,WebSocket 是 ws://(加密版是 wss://)。


3. 它有什么作用?(核心优势)

WebSocket 解决了 HTTP 协议在实时性方面的巨大缺陷。

在 WebSocket 出现之前,为了实现"即时通讯",开发者通常使用轮询 (Polling):客户端每隔几秒钟问一次服务器"有数据吗?"。这种方式不仅延迟高,而且浪费流量和服务器资源(因为大部分时间都在问"有没有",而答案是"没有")。

WebSocket 的核心作用:

  1. 服务器主动推送:服务器一旦产生新数据,可以立刻推送到客户端,无需客户端请求。

  2. 低延迟:省去了 HTTP 建立连接和发送臃肿头部的开销,数据传输极快。

  3. 减少网络开销:数据包头非常小,适合高频传输。


4. WebSocket vs HTTP 对比表

特性 HTTP WebSocket
通信方向 单向(客户端请求 -> 服务器响应) 双向(全双工,双方均可主动发送)
连接状态 无状态,请求结束即断开 有状态,连接一直保持直到关闭
实时性 较差(依赖轮询) 极高
开销 高(每次都要带完整的 HTTP 头) (握手后,数据帧头部很小)
适用场景 获取静态资源、普通的 API 调用 聊天、实时数据、游戏

5. 它的使用场景是什么?

只要场景中包含**"实时"、"即时"、"高频更新"**这些关键词,通常就是 WebSocket 的用武之地:

  • 即时通讯 (IM) / 聊天室

    • 微信网页版、Slack、客服聊天窗口。你需要立刻看到对方发来的消息。
  • 实时数据大屏 / 股票基金

    • 金融交易软件、加密货币价格走势。价格每毫秒都在变,不能等用户刷新。
  • 多人在线游戏

    • 《王者荣耀》、网页版 .io 游戏。你的操作需要立刻同步给其他玩家,延迟必须极低。
  • 协同编辑

    • 腾讯文档、Google Docs、Notion。当你看到光标在移动,或者别人输入的字实时出现在你屏幕上时,背后通常是 WebSocket。
  • 体育赛事直播

    • 文字直播比分更新、弹幕系统。
  • 系统通知

    • 你在浏览网页时,右上角突然弹出"您有一条新消息"。

总结

  • WebSocket 是为了实时通信而生的。

  • 它让 Web 拥有了像桌面程序一样即时响应的能力。

  • 它打破了"请求-响应"的传统模式,实现了服务器对浏览器的主动推送

在 Java 中使用 WebSocket,核心在于理解它的生命周期。无论你使用原生 Java (JSR 356)、SSM 还是 Spring Boot,流程本质上是一样的,只是配置方式("粘合剂")不同。

为了让你最快上手,我们先看通用的工作流程 ,然后重点讲解目前最主流的 Spring Boot 写法,最后对比一下 SSM 的区别。


6. WebSocket 的核心工作流程

一个标准的 WebSocket 服务端通常包含 4 个生命周期事件。想象你在接电话:

  1. 连接建立 (Open):电话接通了。你需要保存这个连接(Session),以便后续能找到人。

  2. 收到消息 (Message):对方说话了。你处理消息,然后可能回复。

  3. 发生错误 (Error):信号不好或出错。记录日志。

  4. 连接关闭 (Close):挂断电话。你需要把这个连接从列表里移除。


7. Spring Boot 中的使用 (最推荐,最常用)

Spring Boot 极大地简化了配置。我们通常使用 注解式 (Annotation) 的方式,这种方式属于 Java 标准 API (JSR 356) 的风格,非常直观。

第一步:引入依赖 (pom.xml)

你需要引入 spring-boot-starter-websocket

XML 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
第二步:开启 WebSocket 支持 (配置类)

写一个配置类,向 Spring 容器注册 ServerEndpointExporter。它的作用是自动扫描并注册所有带有 @ServerEndpoint 注解的类。

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
第三步:编写核心业务逻辑 (ServerEndpoint)

这是最关键的部分。这就像写一个 Controller,但是处理的是长连接。

java 复制代码
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @ServerEndpoint: 将此类标记为 WebSocket 端点
 * "/ws/{userId}": 客户端连接地址,例如 ws://localhost:8080/ws/1001
 */
@Component
@ServerEndpoint("/ws/{userId}") 
public class WebSocketServer {

    // 静态变量,用来记录当前在线连接数。应该设计为线程安全的。
    private static ConcurrentHashMap<String, Session> webSocketMap = new ConcurrentHashMap<>();

    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String userId;

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        
        // 加入 Map 中,绑定 userId 和 session,方便后续点对点发送
        webSocketMap.put(userId, session);
        System.out.println("用户 " + userId + " 已连接,当前在线人数: " + webSocketMap.size());
        
        //以此可以主动给客户端发一条消息
        sendMessage("连接成功!欢迎你," + userId);
    }

    /**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("收到用户 " + userId + " 的消息: " + message);
        // 这里可以写逻辑,比如回复消息,或者转发给别人
        sendMessage("服务端已收到你的消息:" + message);
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        if (webSocketMap.containsKey(userId)) {
            webSocketMap.remove(userId);
            System.out.println("用户 " + userId + " 退出,当前在线人数: " + webSocketMap.size());
        }
    }

    /**
     * 发生错误时调用
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("用户 " + userId + " 发生错误");
        error.printStackTrace();
    }

    /**
     * 自定义方法:实现服务器主动推送
     */
    public void sendMessage(String message) {
        try {
            // getBasicRemote().sendText() 是同步发送
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

8. SSM (Spring MVC) 中的使用

在 SSM 这种较老的架构中,使用 @ServerEndpoint 也是可以的,但更常见的是使用 Spring 自己的接口 WebSocketHandler。这需要更多的配置。

主要区别:Spring Boot 是自动装配,SSM 需要手动在 XML 或 Java Config 中注册。

流程:
  1. 创建处理器 :创建一个类实现 WebSocketHandler 接口(或者继承 TextWebSocketHandler)。

    • 这里面没有 @OnOpen 这种注解,而是要重写 afterConnectionEstablishedhandleTextMessage 等方法。
  2. 创建拦截器 (可选) :实现 HandshakeInterceptor,用于在握手阶段拦截请求(比如做身份验证)。

  3. 配置映射:告诉 Spring,哪个 URL 对应哪个处理器。

SSM 配置示例 (Java Config 方式):

Java

复制代码
@Configuration
@EnableWebSocket // 开启 WebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 将 /ws/myHandler 路径映射到 MyHandler 这个处理器
        registry.addHandler(new MyHandler(), "/ws/myHandler")
                .addInterceptors(new MyHandshakeInterceptor()) // 添加拦截器
                .setAllowedOrigins("*"); // 允许跨域
    }
}

总结 SSM vs Spring Boot:

核心逻辑代码(接收消息、发送消息)是一样的。区别在于 SSM 往往需要通过实现 Spring 的接口 (WebSocketHandler) 来写,且配置稍微繁琐;而 Spring Boot 推荐使用标准的 JSR 356 注解 (@ServerEndpoint),配合自动配置,代码更简洁。


9. Java WebSocket 常用语法速查

无论哪个框架,你主要打交道的对象就是 Session

动作 代码 (基于 Session 对象) 说明
发送文本 session.getBasicRemote().sendText("Hello"); 同步发送,最常用
发送对象 session.getBasicRemote().sendObject(obj); 需要配置编码器
获取参数 @PathParam("id") String id @OnOpen 中获取 URL 路径参数
获取 ID session.getId() 获取当前连接的唯一标识
判断状态 session.isOpen() 发送前最好检查一下连接是否开着
关闭连接 session.close() 服务端主动断开

10. 如何测试?(简单的前端代码)

你可以直接在浏览器的控制台 (F12 -> Console) 或者写一个简单的 HTML 文件来测试你的 Java 后端:

JavaScript

复制代码
// 1. 建立连接 (注意是 ws 协议)
var ws = new WebSocket("ws://localhost:8080/ws/1001");

// 2. 监听连接成功
ws.onopen = function() {
    console.log("连接成功!");
    ws.send("你好,我是客户端!"); // 发送消息
};

// 3. 监听收到消息
ws.onmessage = function(evt) {
    console.log("收到服务端回复: " + evt.data);
};

// 4. 监听关闭
ws.onclose = function() {
    console.log("连接关闭");
};

总结建议

  1. 如果你在写新项目 :直接使用 Spring Boot + @ServerEndpoint 注解。这是最简单、最符合直觉的方式。

  2. 如果你在维护 SSM 老项目 :查找项目中是否实现了 WebSocketHandler 接口,那是 SSM 的标准做法。

  3. 进阶 :上述是"原生"WebSocket。如果你要做复杂的聊天室(群聊、点对点、订阅频道),Spring Boot 还封装了 STOMP 协议(基于 WebSocket 的子协议),功能更强大,但上手稍微难一点。

相关推荐
小李独爱秋1 小时前
计算机网络经典问题透视——IP电话的两大主要信令标准各有何特点?
网络协议·tcp/ip·计算机网络·ip电话
嘿嘿嘿x31 小时前
Modbus TCP 数据结构(发送和返回/读/写)
数据结构·网络协议·tcp/ip
摘星编程1 小时前
React Native for OpenHarmony 实战:NetInfo 网络状态详解
网络·react native·react.js
数通工程师2 小时前
进阶指南:如何利用 SecureCRT 打造“一键式”自动化数据采集方案?
运维·网络·网络协议·tcp/ip·自动化·运维开发
这儿有一堆花2 小时前
互联网通信的双引擎:全面解析 TCP/IP 与 UDP
网络协议·tcp/ip·udp
早日退休!!!2 小时前
RDMA(Remote Direct Memory Access)技术原理与工程实践报告
网络
yzs872 小时前
GreenPlum/Cloudberry UDP数据连接及接收缓存
网络·网络协议·缓存·udp
txinyu的博客2 小时前
连接池问题
服务器·网络·c++
YYYing.2 小时前
【计算机网络 | 第七篇】计网之传输层(一)—— 传输层概述与协议头分析
服务器·网络·网络协议·tcp/ip·计算机网络·udp