零基础入门 WebSocket:从原理到 Java 实战

WebSocket 是实现客户端与服务端全双工双向通信的关键技术,彻底解决了传统 HTTP 协议 "请求 - 响应" 模式下无法主动推送消息的问题。本文会从核心概念、工作原理到 Java 实战,帮你从零掌握 WebSocket 的使用。

一、先搞懂:WebSocket 是什么?

1.1 核心定义

WebSocket 是一种基于 TCP 的应用层协议,能在单个 TCP 连接上建立全双工通信(客户端和服务端可同时发消息,像打电话一样),而 HTTP 是半双工(客户端发请求,服务端才响应,像对讲机)。

1.2 为什么需要 WebSocket?

传统 HTTP 实现 "实时通信" 的痛点:

  • 轮询:客户端频繁发请求问服务端 "有没有新消息",浪费带宽和服务器资源;
  • 长轮询:服务端 hold 住请求直到有数据,仍有连接开销;
  • WebSocket:一次握手建立连接,永久保持(除非主动断开),双向实时传数据,开销极低。

1.3 WebSocket 核心特点

  • 基于 TCP,与 HTTP 兼容(握手阶段用 HTTP);
  • 全双工通信,实时性强;
  • 轻量级协议,数据帧格式简单;
  • 支持跨域,可通过 CORS 配置;
  • 常用场景:聊天系统、实时监控、弹幕、股票行情等。

二、WebSocket 工作流程(通俗版)

  1. 握手阶段 :客户端通过 HTTP 请求(带 Upgrade: websocket 头)向服务端发起 "升级连接" 请求;
  2. 建立连接:服务端同意升级后,HTTP 连接转为 WebSocket 长连接;
  3. 双向通信:连接建立后,客户端 / 服务端可随时发消息,无需重复握手;
  4. 断开连接:任意一方主动关闭,或连接超时 / 异常时断开。

三、Java 实现 WebSocket 实战(基于 Spring Boot)

Spring Boot 对 WebSocket 有成熟的封装,无需手动处理底层协议,是新手最易上手的方式。

3.1 环境准备

3.1.1 引入 Maven 依赖

pom.xml 中添加 WebSocket 核心依赖:

XML 复制代码
<!-- Spring Boot WebSocket 依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
    <!-- 无需指定版本,Spring Boot 父工程已管理 -->
</dependency>
<!-- Web 依赖(用于测试页面) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
3.1.2 环境要求
  • JDK 8 及以上;
  • Spring Boot 2.x/3.x(本文以 3.2.x 为例);
  • 任意 IDE(IDEA/Eclipse)。

3.2 核心代码实现

步骤 1:配置 WebSocket 开启

创建配置类,启用 WebSocket 并注册处理器:

java 复制代码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;

@Configuration
@EnableWebSocket // 开启 WebSocket 支持
public class WebSocketConfig implements WebSocketConfigurer {

    // 注入自定义的 WebSocket 处理器
    private final MyWebSocketHandler myWebSocketHandler;

    // 构造器注入(Spring 自动装配)
    public WebSocketConfig(MyWebSocketHandler myWebSocketHandler) {
        this.myWebSocketHandler = myWebSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册处理器:指定访问路径 + 允许跨域
        registry.addHandler(myWebSocketHandler, "/ws/chat")
                .setAllowedOrigins("*"); // 开发环境允许所有跨域,生产需指定具体域名
    }
}
步骤 2:实现 WebSocket 处理器(核心)

处理器负责处理 "连接建立、接收消息、发送消息、连接关闭" 等核心逻辑:

java 复制代码
import org.springframework.stereotype.Component;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义 WebSocket 处理器
 * 处理客户端的连接、消息收发、连接关闭
 */
@Component
public class MyWebSocketHandler extends TextWebSocketHandler {

    // 存储在线会话(ConcurrentHashMap 保证线程安全)
    private static final ConcurrentHashMap<String, WebSocketSession> ONLINE_SESSIONS = new ConcurrentHashMap<>();

    /**
     * 连接建立成功触发
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 获取客户端唯一标识(这里用 sessionId,实际可替换为用户ID)
        String sessionId = session.getId();
        ONLINE_SESSIONS.put(sessionId, session);
        System.out.println("客户端[" + sessionId + "]已连接,当前在线数:" + ONLINE_SESSIONS.size());
        // 给客户端发送欢迎消息
        sendMessageToSingle(session, "恭喜,WebSocket 连接成功!");
    }

    /**
     * 接收客户端消息触发
     */
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String sessionId = session.getId();
        String receivedMsg = message.getPayload(); // 获取客户端发送的消息
        System.out.println("收到客户端[" + sessionId + "]的消息:" + receivedMsg);

        // 场景1:给发送者回显消息
        sendMessageToSingle(session, "服务端已收到你的消息:" + receivedMsg);

        // 场景2:广播消息(发给所有在线客户端)
        broadcastMessage("客户端[" + sessionId + "]:" + receivedMsg);
    }

    /**
     * 连接关闭触发
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String sessionId = session.getId();
        ONLINE_SESSIONS.remove(sessionId);
        System.out.println("客户端[" + sessionId + "]已断开连接,当前在线数:" + ONLINE_SESSIONS.size());
        // 广播下线通知
        broadcastMessage("客户端[" + sessionId + "]已离开");
    }

    /**
     * 发生异常触发
     */
    @Override
    public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
        String sessionId = session.getId();
        System.out.println("客户端[" + sessionId + "]连接异常:" + exception.getMessage());
        if (session.isOpen()) {
            session.close(); // 异常时关闭连接
        }
        ONLINE_SESSIONS.remove(sessionId);
    }

    // ========== 工具方法:发送消息 ==========
    /**
     * 给单个客户端发消息
     */
    private void sendMessageToSingle(WebSocketSession session, String msg) throws IOException {
        if (session.isOpen()) { // 确保连接未关闭
            session.sendMessage(new TextMessage(msg));
        }
    }

    /**
     * 广播消息(发给所有在线客户端)
     */
    private void broadcastMessage(String msg) {
        // 遍历所有在线会话,逐个发送
        ONLINE_SESSIONS.forEach((sessionId, session) -> {
            try {
                sendMessageToSingle(session, msg);
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }
}
步骤 3:编写测试页面(前端)

resources/static 目录下创建 index.html(Spring Boot 会自动映射静态资源),用于测试 WebSocket 通信:

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket 测试</title>
    <style>
        #msgBox { width: 400px; height: 300px; border: 1px solid #ccc; padding: 10px; overflow-y: auto; margin-bottom: 10px; }
        #msgInput { width: 300px; padding: 5px; }
        button { padding: 5px 10px; }
    </style>
</head>
<body>
    <div id="msgBox"></div>
    <input type="text" id="msgInput" placeholder="请输入消息">
    <button onclick="sendMsg()">发送消息</button>
    <button onclick="closeConn()">关闭连接</button>

    <script>
        let ws = null;
        // 1. 建立连接
        function connect() {
            // 判断浏览器是否支持 WebSocket
            if (!window.WebSocket) {
                alert("你的浏览器不支持 WebSocket!");
                return;
            }
            // 连接服务端(ws://对应http,wss://对应https)
            ws = new WebSocket("ws://localhost:8080/ws/chat");
            
            // 连接成功回调
            ws.onopen = function() {
                appendMsg("WebSocket 连接成功!");
            };
            
            // 接收服务端消息回调
            ws.onmessage = function(event) {
                appendMsg("服务端:" + event.data);
            };
            
            // 连接关闭回调
            ws.onclose = function() {
                appendMsg("WebSocket 连接已关闭!");
            };
            
            // 连接异常回调
            ws.onerror = function(error) {
                appendMsg("连接异常:" + error);
            };
        }

        // 2. 发送消息
        function sendMsg() {
            const input = document.getElementById("msgInput");
            const msg = input.value.trim();
            if (!msg || !ws) {
                alert("请先连接且输入消息!");
                return;
            }
            ws.send(msg); // 发送消息到服务端
            appendMsg("我:" + msg);
            input.value = ""; // 清空输入框
        }

        // 3. 关闭连接
        function closeConn() {
            if (ws) {
                ws.close();
                ws = null;
            }
        }

        // 辅助:追加消息到页面
        function appendMsg(msg) {
            const msgBox = document.getElementById("msgBox");
            msgBox.innerHTML += msg + "<br>";
            // 滚动到底部
            msgBox.scrollTop = msgBox.scrollHeight;
        }

        // 页面加载时自动连接
        window.onload = connect;
    </script>
</body>
</html>

3.3 运行测试

  1. 启动 Spring Boot 项目(默认端口 8080);
  2. 打开浏览器访问 http://localhost:8080/index.html
  3. 测试操作:
    • 页面加载后自动建立连接,消息框显示 "WebSocket 连接成功";
    • 输入消息点击 "发送",服务端接收后会回显 + 广播;
    • 打开多个浏览器标签页,可看到广播消息(所有页面都能收到);
    • 点击 "关闭连接",服务端会提示客户端断开。

四、核心知识点解析

4.1 关键类 / 接口

  • @EnableWebSocket:开启 Spring Boot 的 WebSocket 支持;
  • WebSocketHandler:核心处理器接口,TextWebSocketHandler 是其文本消息实现类;
  • WebSocketSession:代表一个客户端连接会话,包含连接状态、发送消息等方法;
  • ConcurrentHashMap:存储在线会话,保证多线程下的线程安全(WebSocket 是多线程处理)。

4.2 核心方法

方法 作用
afterConnectionEstablished 客户端连接成功后触发
handleTextMessage 接收客户端文本消息时触发
afterConnectionClosed 客户端断开连接后触发
session.sendMessage() 服务端给客户端发消息

4.3 常见问题与解决方案

  1. 跨域问题 :生产环境不要用 setAllowedOrigins("*"),需指定具体域名(如 http://localhost:8080);

  2. 连接断开 :可在前端加 "重连机制",监听 onclose 后定时重试连接;

  3. 消息大小限制 :默认 WebSocket 消息大小有限制,可在配置类中设置:

    java 复制代码
    registry.addHandler(...)
            .setAllowedOrigins("*")
            .setMessageSizeLimit(1024 * 1024); // 限制消息最大1MB

五、进阶方向

  1. 点对点通信 :结合用户认证(如 Token),将 sessionId 替换为用户 ID,实现 "指定用户发消息";
  2. STOMP 协议:基于 WebSocket 的高级协议,支持 "订阅 - 发布" 模式(如按房间 / 主题收发消息);
  3. 分布式 WebSocket:集群环境下,需结合 Redis 发布订阅实现跨节点消息同步;
  4. 心跳检测:客户端 / 服务端定时发心跳包,检测无效连接并自动清理。

总结

  1. 核心概念:WebSocket 是全双工通信协议,通过一次握手建立长连接,解决 HTTP 实时通信痛点;
  2. Java 实现 :Spring Boot 中通过 @EnableWebSocket 开启支持,自定义 TextWebSocketHandler 处理核心通信逻辑;
  3. 关键要点 :会话管理需保证线程安全(用 ConcurrentHashMap),消息收发要判断连接状态,生产环境需处理跨域和重连。

通过本文的实战代码,你可以快速搭建一个基础的 WebSocket 通信系统,在此基础上扩展聊天、实时监控等业务功能。

相关推荐
爬山算法2 小时前
Hibernate(54)Hibernate中的批量更新如何实现?
java·后端·hibernate
芯有所享2 小时前
【芯片设计中的ARM CoreSight IP:全方位调试与追踪解决方案】
arm开发·经验分享·网络协议·tcp/ip
老毛肚2 小时前
Spring 4.0 Spring MVC。
java·spring·mvc
sheji34162 小时前
【开题答辩全过程】以 某高校教学仪器设备管理系统设计与开发为例,包含答辩的问题和答案
java
小宇的天下2 小时前
Calibre nmDRC-H 层级化 DRC
java·服务器·前端
毕设源码-钟学长2 小时前
【开题答辩全过程】以 面向社区的网上书店为例,包含答辩的问题和答案
java
sunddy_x2 小时前
Spring事务
java·spring·mybatis
量子炒饭大师2 小时前
【C++入门】骇客数据面向对象的灵魂锚点——【类与对象】this指针篇
java·c++·dubbo·this·this指针
J_liaty2 小时前
Spring Boot整合Shiro实现权限认证
java·spring boot·后端·shiro