webSocket入门

一、WebSocket 是什么?核心定位

WebSocket 是一种基于 TCP 协议的全双工通信协议 ,也是 HTML5 的核心特性之一,它的诞生是为了解决 HTTP 协议的通信缺陷,专门用于实现客户端 ↔ 服务端的实时双向通信


二、HTTP 协议的痛点(为什么需要 WebSocket)

我们日常开发的接口都是基于 HTTP/HTTPS 协议 ,HTTP 协议有 3 个核心特性,这也是它的「痛点」,完全无法满足实时通信需求:

HTTP 协议的 3 个核心痛点

  1. 单向通信 :通信永远是 客户端主动发起请求 → 服务端被动响应 ,服务端永远不能主动给客户端发消息
  2. 无状态连接:每次请求都是全新的连接,服务端不会记住上一次请求的任何信息,请求结束后 TCP 连接就断开;
  3. 短连接模式:一次请求对应一次响应,响应完成后连接立即关闭,无法持续通信。

传统「伪实时」解决方案(治标不治本)

为了实现「服务端数据变化后,客户端能及时感知」的需求,在 WebSocket 出现前,行业内都是用「轮询」方案,典型的有 2 种:

  1. 普通轮询 :客户端每隔一段时间(比如 1 秒)通过 Ajax 发起一次请求,查询服务端是否有新数据;
    • 缺点:极度浪费资源!不管服务端有没有新数据,请求都会发起,无效请求占比极高,服务端压力大、客户端有延迟。
  2. 长轮询 (long polling) :客户端发起请求后,服务端会挂起请求不返回 ,直到有新数据 / 超时才响应,客户端收到响应后立刻发起新请求;
    • 缺点:虽然比普通轮询好,但本质还是 HTTP 请求,依然是「单向通信」,且会产生大量无效的请求头、连接开销,实时性依然不足。

结论

**轮询是「无奈的妥协方案」,WebSocket 是「真正的解决方案」**轮询能实现「准实时」,但做不到「真实时」;WebSocket 从协议层面彻底解决了 HTTP 的单向通信问题,是实时通信的最优解。


三、WebSocket 核心特性

  1. 全双工通信 :连接建立后,客户端可以主动发消息给服务端,服务端也可以主动发消息给客户端,双向对等通信,实时性拉满;
  2. 一次连接,长久有效 :WebSocket 是长连接,客户端和服务端完成「握手」后,TCP 连接会一直保持,直到某一方主动断开;
  3. 极少的通信开销
    • 握手阶段基于 HTTP 协议,只做一次连接认证,后续通信都是纯数据传输;
    • 没有 HTTP 那种臃肿的请求头 / 响应头,数据传输体积小,带宽占用极低;
  4. 基于 TCP 协议,安全可靠:底层是稳定的 TCP 协议,支持断线重连,数据传输有保障;
  5. 跨域友好:WebSocket 本身支持跨域,无需像 Ajax 那样配置 CORS 跨域规则;
  6. 兼容性好:所有现代浏览器(Chrome/Firefox/Edge)、移动端浏览器都原生支持,老浏览器也有成熟的降级方案。

WebSocket VS HTTP 对比表

特性 HTTP 协议 WebSocket 协议
通信方式 单向通信(客户端→服务端) 全双工通信(双向对等)
连接方式 短连接(请求完即断开) 长连接(一次连接,长久有效)
通信主动性 客户端主动请求,服务端被动响应 客户端 / 服务端 均可主动发消息
数据开销 每次请求都带完整 HTTP 头,开销大 仅握手有开销,后续纯数据传输,开销极小
实时性 差(轮询有延迟) 极高(毫秒级实时推送)

四、WebSocket 工作原理(握手 + 通信,超通俗讲解)

WebSocket 的工作流程只有 2 个阶段连接建立(握手阶段) + 数据通信阶段,非常简单,不用记复杂的源码,理解流程即可。

阶段 1:连接建立 → 基于 HTTP 的「握手」

WebSocket 不能凭空建立连接,它的第一次连接(握手)是基于 HTTP 协议完成的,这个过程叫「WebSocket 握手」,步骤如下:

  1. 客户端发起一个 特殊的 HTTP GET 请求 ,请求地址不是接口,而是 ws://xxxwss://xxx(对应 HTTP/HTTPS);
  2. 请求头中会携带 3 个核心标识,告诉服务端「我要升级为 WebSocket 连接」:
    • Upgrade: websocket :声明要升级协议为 WebSocket
    • Connection: Upgrade :声明这是一个升级连接的请求
    • Sec-WebSocket-Key :客户端生成的随机密钥,用于服务端校验身份
  3. 服务端收到请求后,校验通过,返回一个 HTTP 101 响应码(表示「协议切换成功」);
  4. 此时,HTTP 连接正式升级为 WebSocket 连接,后续所有通信都不再走 HTTP 协议,而是纯 TCP 的双向数据传输。

补充:ws:// 是 WebSocket 的明文协议,wss:// 是加密协议(基于 SSL/TLS),生产环境一律用 wss://,和 HTTPS 同理。

阶段 2:数据通信 → 全双工的实时消息传输

握手成功后,TCP 长连接建立,此时:

  • 客户端可以随时通过 send() 方法给服务端发消息;
  • 服务端可以随时主动给客户端推送消息;
  • 消息格式支持:字符串、JSON、二进制数据(文件 / 图片),满足所有业务场景;
  • 双方都可以通过 onclose 事件感知连接断开,支持主动关闭连接。

阶段 3:连接断开

任意一方调用 close() 方法,即可主动断开 WebSocket 连接,TCP 连接释放,通信结束。

五、WebSocket 核心应用场景(这些场景必用 WebSocket)

只要业务需求中包含 「实时性」「双向通信」,WebSocket 就是最优选择,没有之一,典型的核心场景如下:

在线聊天 / 即时通讯:网页版微信、客服聊天框、群聊、私信;

实时数据看板:后台监控大屏、股票行情 / K 线图、区块链价格、物联网设备数据实时展示;

协同编辑:多人在线文档、在线表格、在线画板(比如腾讯文档、飞书文档);

消息推送:系统通知、订单状态推送、点赞评论提醒、物流轨迹实时更新;

实时互动类业务:在线答题、直播弹幕、在线游戏、视频会议;

高频数据反馈:网约车实时定位、外卖配送轨迹、共享单车位置更新。

总结:凡是需要「服务端主动给客户端推数据」的场景,都优先用 WebSocket

SpringBoot 整合 WebSocket

步骤 1:引入 Maven 依赖(SpringBoot 2.x/3.x 通用)
java 复制代码
<!-- SpringBoot 整合 WebSocket 核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

原生 WebSocket(@ServerEndpoint)

这是 SpringBoot 官方推荐的方式 ,基于 JSR356 标准(Java WebSocket 规范),通过 @ServerEndpoint 注解快速创建 WebSocket 服务端,无需配置类、无需额外开发,注解式开发,和 Controller 写法类似,极度简洁,适合单服务、单体项目,是日常开发的首选!

步骤 2:编写 WebSocket 核心服务类
java 复制代码
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * WebSocket服务端核心类
 * @ServerEndpoint 注解:声明这是一个WebSocket服务端,指定访问路径为 /ws/connect
 */
@Slf4j
@Component
@ServerEndpoint("/ws/connect")
public class WebSocketServer {

    // 统计当前在线连接数(线程安全)
    private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);

    // 存储所有在线的客户端连接(线程安全的Map) key:会话ID  value:当前WebSocket对象
    private static final Map<String, WebSocketServer> CLIENT_MAP = new ConcurrentHashMap<>();

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

    /**
     * 【核心】连接建立成功时触发的方法(客户端连接成功后执行)
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        String sessionId = session.getId();
        // 将当前连接存入Map
        CLIENT_MAP.put(sessionId, this);
        // 在线数+1
        int count = ONLINE_COUNT.incrementAndGet();
        log.info("客户端【{}】连接成功,当前在线人数:{}", sessionId, count);
        // 给客户端发送连接成功的消息
        sendMessage("恭喜你,WebSocket连接成功!当前在线人数:" + count);
    }

    /**
     * 【核心】收到客户端发送的消息时触发的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        String sessionId = session.getId();
        log.info("收到客户端【{}】的消息:{}", sessionId, message);
        // 业务处理:1.给当前客户端回复消息 2.广播消息给所有在线客户端
        sendMessage("服务端已收到你的消息:" + message);
        broadcastMessage("【广播】客户端【"+sessionId+"】说:"+message);
    }

    /**
     * 【核心】连接关闭时触发的方法(客户端断开连接后执行)
     */
    @OnClose
    public void onClose() {
        String sessionId = this.session.getId();
        // 从Map中移除当前连接
        CLIENT_MAP.remove(sessionId);
        // 在线数-1
        int count = ONLINE_COUNT.decrementAndGet();
        log.info("客户端【{}】断开连接,当前在线人数:{}", sessionId, count);
    }

    /**
     * 【核心】连接发生错误时触发的方法
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("客户端【{}】连接发生错误:", session.getId(), error);
    }

    // ========== 自定义工具方法 ==========
    /**
     * 给当前客户端发送消息
     */
    public void sendMessage(String message) {
        try {
            this.session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error("发送消息失败:", e);
        }
    }

    /**
     * 广播消息:给所有在线的客户端发送消息(核心业务常用)
     */
    public static void broadcastMessage(String message) {
        for (WebSocketServer webSocket : CLIENT_MAP.values()) {
            webSocket.sendMessage(message);
        }
    }
}
步骤 3:编写前端测试页面
html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket测试页面</title>
</head>
<body>
<h3>WebSocket实时通信测试</h3>
<div>
    <input type="text" id="msgInput" placeholder="请输入要发送的消息">
    <button onclick="sendMsg()">发送消息</button>
    <button onclick="closeConn()">断开连接</button>
</div>
<div style="margin-top: 20px;">
    <h4>消息接收区:</h4>
    <div id="msgBox" style="width: 600px;height: 400px;border:1px solid #ccc;padding:10px;overflow-y:auto;"></div>
</div>

<script>
    // 1. 声明WebSocket对象,连接服务端(注意:ws://对应HTTP,wss://对应HTTPS,端口是你的项目端口)
    let ws = new WebSocket("ws://localhost:8080/ws/connect");
    let msgBox = document.getElementById("msgBox");

    // 2. 连接成功的回调
    ws.onopen = function() {
        appendMsg("✅ 客户端:WebSocket连接成功!");
    }

    // 3. 收到服务端消息的回调【核心】
    ws.onmessage = function(e) {
        appendMsg("🟢 服务端:" + e.data);
    }

    // 4. 连接关闭的回调
    ws.onclose = function() {
        appendMsg("❌ 客户端:WebSocket连接已断开!");
    }

    // 5. 连接错误的回调
    ws.onerror = function(error) {
        appendMsg("❌ 客户端:连接发生错误 → " + error);
    }

    // 发送消息给服务端
    function sendMsg() {
        let msg = document.getElementById("msgInput").value;
        if (!msg) return;
        ws.send(msg);
        appendMsg("🔵 我:" + msg);
        document.getElementById("msgInput").value = "";
    }

    // 主动断开连接
    function closeConn() {
        ws.close();
    }

    // 追加消息到页面
    function appendMsg(msg) {
        let p = document.createElement("p");
        p.style.margin = "5px 0";
        p.innerText = new Date().toLocaleString() + " → " + msg;
        msgBox.appendChild(p);
        // 滚动到底部
        msgBox.scrollTop = msgBox.scrollHeight;
    }
</script>
</body>
</html>
相关推荐
serve the people几秒前
python环境搭建 (十二) pydantic和pydantic-settings类型验证与解析
java·网络·python
lekami_兰15 分钟前
Java 并发工具类详解:4 大核心工具 + 实战场景,告别 synchronized
java·并发工具
_运维那些事儿20 分钟前
VM环境的CI/CD
linux·运维·网络·阿里云·ci/cd·docker·云计算
有位神秘人21 分钟前
Android中Notification的使用详解
android·java·javascript
云小逸24 分钟前
【nmap源码学习】 Nmap网络扫描工具深度解析:从基础参数到核心扫描逻辑
网络·数据库·学习
tb_first1 小时前
LangChain4j简单入门
java·spring boot·langchain4j
Trouvaille ~1 小时前
【Linux】UDP Socket编程实战(一):Echo Server从零到一
linux·运维·服务器·网络·c++·websocket·udp
独自破碎E2 小时前
【BISHI9】田忌赛马
android·java·开发语言
范纹杉想快点毕业2 小时前
实战级ZYNQ中断状态机FIFO设计
java·开发语言·驱动开发·设计模式·架构·mfc
咖丨喱2 小时前
IP校验和算法解析与实现
网络·tcp/ip·算法