一、WebSocket 是什么?核心定位
WebSocket 是一种基于 TCP 协议的全双工通信协议 ,也是 HTML5 的核心特性之一,它的诞生是为了解决 HTTP 协议的通信缺陷,专门用于实现客户端 ↔ 服务端的实时双向通信。
二、HTTP 协议的痛点(为什么需要 WebSocket)
我们日常开发的接口都是基于 HTTP/HTTPS 协议 ,HTTP 协议有 3 个核心特性,这也是它的「痛点」,完全无法满足实时通信需求:
HTTP 协议的 3 个核心痛点
- 单向通信 :通信永远是 客户端主动发起请求 → 服务端被动响应 ,服务端永远不能主动给客户端发消息;
- 无状态连接:每次请求都是全新的连接,服务端不会记住上一次请求的任何信息,请求结束后 TCP 连接就断开;
- 短连接模式:一次请求对应一次响应,响应完成后连接立即关闭,无法持续通信。
传统「伪实时」解决方案(治标不治本)
为了实现「服务端数据变化后,客户端能及时感知」的需求,在 WebSocket 出现前,行业内都是用「轮询」方案,典型的有 2 种:
- 普通轮询 :客户端每隔一段时间(比如 1 秒)通过 Ajax 发起一次请求,查询服务端是否有新数据;
- 缺点:极度浪费资源!不管服务端有没有新数据,请求都会发起,无效请求占比极高,服务端压力大、客户端有延迟。
- 长轮询 (long polling) :客户端发起请求后,服务端会挂起请求不返回 ,直到有新数据 / 超时才响应,客户端收到响应后立刻发起新请求;
- 缺点:虽然比普通轮询好,但本质还是 HTTP 请求,依然是「单向通信」,且会产生大量无效的请求头、连接开销,实时性依然不足。
结论
**轮询是「无奈的妥协方案」,WebSocket 是「真正的解决方案」**轮询能实现「准实时」,但做不到「真实时」;WebSocket 从协议层面彻底解决了 HTTP 的单向通信问题,是实时通信的最优解。
三、WebSocket 核心特性
- 全双工通信 :连接建立后,客户端可以主动发消息给服务端,服务端也可以主动发消息给客户端,双向对等通信,实时性拉满;
- 一次连接,长久有效 :WebSocket 是长连接,客户端和服务端完成「握手」后,TCP 连接会一直保持,直到某一方主动断开;
- 极少的通信开销 :
- 握手阶段基于 HTTP 协议,只做一次连接认证,后续通信都是纯数据传输;
- 没有 HTTP 那种臃肿的请求头 / 响应头,数据传输体积小,带宽占用极低;
- 基于 TCP 协议,安全可靠:底层是稳定的 TCP 协议,支持断线重连,数据传输有保障;
- 跨域友好:WebSocket 本身支持跨域,无需像 Ajax 那样配置 CORS 跨域规则;
- 兼容性好:所有现代浏览器(Chrome/Firefox/Edge)、移动端浏览器都原生支持,老浏览器也有成熟的降级方案。
WebSocket VS HTTP 对比表
| 特性 | HTTP 协议 | WebSocket 协议 |
|---|---|---|
| 通信方式 | 单向通信(客户端→服务端) | 全双工通信(双向对等) |
| 连接方式 | 短连接(请求完即断开) | 长连接(一次连接,长久有效) |
| 通信主动性 | 客户端主动请求,服务端被动响应 | 客户端 / 服务端 均可主动发消息 |
| 数据开销 | 每次请求都带完整 HTTP 头,开销大 | 仅握手有开销,后续纯数据传输,开销极小 |
| 实时性 | 差(轮询有延迟) | 极高(毫秒级实时推送) |
四、WebSocket 工作原理(握手 + 通信,超通俗讲解)
WebSocket 的工作流程只有 2 个阶段 :连接建立(握手阶段) + 数据通信阶段,非常简单,不用记复杂的源码,理解流程即可。
阶段 1:连接建立 → 基于 HTTP 的「握手」
WebSocket 不能凭空建立连接,它的第一次连接(握手)是基于 HTTP 协议完成的,这个过程叫「WebSocket 握手」,步骤如下:
- 客户端发起一个 特殊的 HTTP GET 请求 ,请求地址不是接口,而是
ws://xxx或wss://xxx(对应 HTTP/HTTPS); - 请求头中会携带 3 个核心标识,告诉服务端「我要升级为 WebSocket 连接」:
Upgrade: websocket:声明要升级协议为 WebSocketConnection: Upgrade:声明这是一个升级连接的请求Sec-WebSocket-Key:客户端生成的随机密钥,用于服务端校验身份
- 服务端收到请求后,校验通过,返回一个 HTTP 101 响应码(表示「协议切换成功」);
- 此时,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>