前言
在 Web 开发中,实时通信场景(如在线聊天、直播弹幕、股票行情推送)越来越普遍,而传统 HTTP 通信因'请求 - 响应'的单向限制,难以满足这类场景的需求。WebSocket 作为一种全双工长连接协议,完美解决了这一痛点,成为 Java 后端开发中实现实时通信的核心技术。本文将全面拆解 WebSocket 的基础认知、工作原理、Java 实现方式、与 HTTP 的核心区别,以及离线消息存储、跨服务器拓展等进阶知识点,帮你一文吃透 WebSocket 所有核心内容。
一、基础认知:WebSocket 是什么?
WebSocket 是一种基于 TCP 的应用层全双工长连接通信协议,核心作用是打破传统 HTTP 单向通信的局限,实现客户端与服务端的实时双向交互。
通俗类比:HTTP 是'你问我答'的电话沟通(你不问,我不答),而 WebSocket 是'面对面聊天'的双向对话(双方可随时开口)。
核心优势:
- 全双工:客户端与服务端可同时收发数据,无需等待对方响应;
- 长连接:一次握手建立连接后,持续有效,直到主动关闭,无需反复建立/断开 TCP 连接;
- 轻量级:数据传输采用极简帧格式,开销远小于 HTTP,适合高频实时通信;
- 原生兼容:基于 HTTP 握手升级实现,可兼容现有 HTTP 网络(防火墙、代理服务器等)。
二、核心原理:WebSocket 从连接到通信的完整流程
WebSocket 并未脱离 TCP/IP 协议栈,而是在 HTTP 基础上做了'协议升级',整个工作流程分为 4 个关键阶段,每一步都决定了它的核心特性,全程兼顾兼容性与高效性。
1. 阶段 1:HTTP 握手升级(连接建立的关键)
WebSocket 无法直接建立连接,必须先通过 HTTP 完成'握手升级'(兼容现有网络环境),具体流程:
- 客户端发起升级请求:发送特殊 HTTP GET 请求,携带核心头信息(Upgrade: websocket、Connection: Upgrade),告知服务端要切换协议,同时携带随机字符串 Sec-WebSocket-Key 用于验证;
- 服务端响应升级:验证通过后,返回 HTTP 101 状态码(Switching Protocols),携带加密后的 Sec-WebSocket-Accept(客户端 Key+ 固定字符串加密),确认协议切换;
- 握手完成:客户端验证 Sec-WebSocket-Accept 通过后,HTTP 连接升级为 WebSocket 连接,后续通信不再走 HTTP 协议。
2. 阶段 2:数据传输(全双工 + 轻量级帧)
握手成功后,双方进入全双工通信模式,核心依赖 WebSocket 的'帧(Frame)'格式——这是它比 HTTP 高效的核心原因。
WebSocket 帧结构极简,仅包含必要字段:FIN(是否为最后一帧)、Opcode(帧类型:文本/二进制/心跳/关闭)、Masked(客户端帧掩码标识)、Payload Length(数据长度)、Masking-Key(客户端帧掩码)、Payload Data(业务数据)。
核心对比:HTTP 每次传输需携带完整请求头(Host、Cookie 等),头信息往往远大于业务数据;WebSocket 帧头仅 2-10 字节,核心传输业务数据,开销极大降低。
3. 阶段 3:连接维护(心跳保活,避免假死)
WebSocket 是长连接,但网络不稳定(防火墙、路由器超时)会导致'假死连接',需通过心跳机制维护:
- Ping 帧(Opcode=9):客户端/服务端定时发送(如每 20 秒),表示'我还在线';
- Pong 帧(Opcode=10):收到 Ping 帧后立即回复,证明连接正常;
- 超时处理:发送 Ping 后指定时间(如 10 秒)未收到 Pong,判定连接失效,触发重连。
4. 阶段 4:优雅关闭(双向确认,避免数据丢失)
连接关闭并非直接断开 TCP,而是通过 Close 帧(Opcode=8)优雅关闭:一方发送 Close 帧(携带关闭原因),另一方回复确认,双方确认后再断开 TCP 连接,避免数据丢失。
三、Java 实现:两种主流方式(可直接复用)
Java 中实现 WebSocket 主要有两种方式,适配不同项目场景,以下为核心简化代码(完整可直接落地)。
方式 1:JSR 356(Java 官方标准,无需额外依赖)
适用于 Tomcat/Jetty 等 Java EE 容器(Tomcat 7+、Jetty 9+ 内置支持),通过注解快速定义端点。
javax.websocket.*;
javax.websocket.server.ServerEndpoint;
java.io.IOException;
{
{
System.out.println( + session.getId());
}
IOException {
System.out.println( + message);
session.getBasicRemote().sendText( + message);
}
{
System.out.println( + session.getId());
}
{
System.err.println( + error.getMessage());
}
}


