一、WebSocket 核心认知(必懂,理解了用法才通透)
1. WebSocket 是什么?
WebSocket 是 HTML5 新增的一种「全双工、持久化」的网络通信协议,协议标识是 ws://(明文)和 wss://(加密,推荐生产环境用),是 HTTP 协议的补充和升级。
WebSocket 是一种全双工持久化通信协议,基于 TCP 实现实时双向通信。其核心原理及与 HTTP 的区别,提供原生 JS 前端完整代码及 Node.js、SpringBoot、FastAPI 后端示例。重点涵盖心跳保活与断线重连机制,解决生产环境静默断开问题,并梳理适用场景与常见面试题,帮助开发者快速落地 WebSocket 开发。
WebSocket 是 HTML5 新增的一种「全双工、持久化」的网络通信协议,协议标识是 ws://(明文)和 wss://(加密,推荐生产环境用),是 HTTP 协议的补充和升级。
HTTP 协议是「单工 / 半双工」、「短连接」、「无状态」的通信模式,有 2 个致命痛点:
这就导致 HTTP 无法实现「实时通信」场景(比如聊天、股票行情、扫码登录、消息推送),之前只能用「轮询 / 长轮询」曲线救国,但效率极低、资源浪费严重。
✅ 全双工通信:连接建立后,客户端 ↔ 服务端 双向平等通信,双方都可以主动向对方发送消息,无需谁请求谁; ✅ 持久连接:一次握手成功后,TCP 连接会一直保持,直到主动断开,没有频繁的连接建立 / 销毁开销; ✅ 轻量级数据传输:通信头信息极小,数据传输效率高; ✅ 无同源限制:客户端和服务端建立连接时,不受浏览器同源策略的限制; ✅ 基于 TCP:底层复用 TCP 协议,稳定性高、传输可靠。
WebSocket 不是替代 HTTP,而是基于 HTTP 完成握手,之后脱离 HTTP 独立通信,二者关系:
不管前端用原生 JS、Vue/React,后端用 Java/Node/PHP/Python,所有 WebSocket 的通信流程完全一致,只有 3 个核心步骤,记住这个流程,所有用法都围绕它展开:
前端是 WebSocket 最核心的使用端,浏览器原生内置了 WebSocket 对象,无需引入任何依赖,直接使用,语法完全标准化,Vue/React/小程序里的用法和原生 JS 一致,只是把代码放到对应的生命周期即可。
// 1. 创建 WebSocket 实例,建立连接(核心) // 语法:new WebSocket(协议地址) // 本地测试:ws://localhost:8080 生产加密:wss://你的域名:端口
let ws = new WebSocket('ws://localhost:8080');
// -------------------------- 2. 核心事件:4 个必用事件(监听连接状态 + 收发消息) --------------------------
/**
* ✅ 事件 1:onopen - 连接成功(握手成功)触发【必写】
* 连接建立后,只会触发 1 次,适合做:初始化、登录鉴权、发送首次请求等
*/
ws.onopen = function () {
console.log('✅ WebSocket 连接成功!');
// 连接成功后,客户端主动给服务端发消息(send 方法)
ws.send('哈喽,服务端,我是客户端,连接成功啦!');
};
/**
* ✅ 事件 2:onmessage - 收到服务端的消息时触发【核心必写】
* 只要服务端给客户端发消息,这个事件就会触发,实时接收消息
* event.data:固定属性,拿到服务端发送的「消息内容」(字符串格式)
*/
ws.onmessage = function (event) {
console.log('📥 收到服务端消息:', event.data);
// 业务处理:比如渲染聊天消息、更新实时数据、处理推送通知等
// 如果服务端发的是 JSON 对象,需要解析:const data = JSON.parse(event.data);
};
/**
* ✅ 事件 3:onclose - 连接关闭时触发【必写】
* 触发场景:主动关闭连接、网络断开、服务端下线、超时等
* event.code:关闭状态码,event.reason:关闭原因
*/
ws.onclose = function (event) {
console.log('❌ WebSocket 连接关闭', event.code, event.reason);
// 业务处理:提示用户、清空实时数据、尝试重连等
};
/**
* ✅ 事件 4:onerror - 连接发生错误时触发【必写】
* 触发场景:地址错误、服务端未启动、网络异常、跨域配置错误等
* 错误发生后,大概率会紧接着触发 onclose 事件
*/
ws.onerror = function (error) {
console.error('❌ WebSocket 连接异常:', error);
};
// -------------------------- 3. 核心方法:2 个必用方法(主动发消息 + 主动关连接) --------------------------
/**
* ✅ 方法 1:ws.send(msg) - 客户端主动发送消息给服务端【核心必写】
* 入参 msg:必须是「字符串类型」!!!
* 注意:如果要发送对象/数组,必须先转 JSON:JSON.stringify({name: '张三', age: 20})
*/
// 示例:发送 JSON 对象
const userInfo = { userId: 1001, userName: '前端小菜鸡' };
ws.send(JSON.stringify(userInfo));
/**
* ✅ 方法 2:ws.close() - 客户端主动关闭连接【必写】
* 触发场景:页面关闭、用户退出、不需要实时通信时(比如离开聊天页)
* 调用后,会触发自身的 onclose 事件,服务端也会收到关闭通知
*/
// 比如:页面销毁时关闭连接(Vue 的 beforeDestroy、React 的 componentWillUnmount)
window.addEventListener('unload', () => {
ws.close();
console.log('📤 客户端主动关闭连接');
});
// -------------------------- 4. 核心属性:1 个常用属性 --------------------------
/**
* ✅ readyState - 查看当前 WebSocket 的「连接状态」【常用】
* 有 4 个固定值,只读,不能修改:
* 0: CONNECTING - 正在连接中(刚 new 出来,还没握手)
* 1: OPEN - 连接成功(可以正常收发消息,最常用)
* 2: CLOSING - 正在关闭中(调用了 close,还没完全断开)
* 3: CLOSED - 连接已关闭(无法收发消息)
*/
console.log('当前连接状态:', ws.readyState);
// 业务中常用:发送消息前判断连接是否正常,避免报错
if (ws.readyState === 1) {
ws.send('确保连接正常时发送的消息');
}
ws.send() 只能传字符串!如果要传对象 / 数组 / 数字,必须用 JSON.stringify() 转成字符串,服务端收到后再用 JSON.parse() 解析,反之同理。ws.readyState === 1,如果连接未建立 / 已关闭,直接 send 会报错。ws.close() 关闭连接,否则会造成「连接泄漏」,多个无效连接占用资源。onerror 事件只是「错误监听」,不能阻止错误发生,错误后大概率会触发 onclose,可以在 onclose 中做「重连逻辑」。WebSocket 是客户端 - 服务端双向协议,必须有服务端配合才能运行,后端的核心职责是:启动 WebSocket 服务、监听客户端连接、接收客户端消息、主动给客户端发消息、关闭连接。
这里提供 3 种主流后端语言的极简完整案例,全部可直接复制运行,代码极简无冗余,适合入门和实战,也是企业开发中最常用的方案:
Node.js 有一个超好用的第三方包 ws,专门处理 WebSocket,轻量、高效、文档友好,无需配置任何服务器,装包即运行。
# 新建文件夹,初始化 npm init -y
# 安装 ws 包
npm install ws
const WebSocket = require('ws');
// 创建 WebSocket 服务,监听 8080 端口
const wss = new WebSocket.Server({ port: 8080 });
console.log('✅ Node.js WebSocket 服务启动成功,监听端口:8080');
// 监听「客户端连接」事件:有客户端连上来就触发
wss.on('connection', (ws) => {
console.log('📌 有一个客户端成功连接');
// 监听「客户端发送的消息」事件:接收客户端消息
ws.on('message', (msg) => {
console.log('📥 收到客户端消息:', msg.toString());
// 服务端主动给「当前连接的客户端」发消息
ws.send(`我收到你的消息啦:${msg.toString()}`);
// 【进阶】广播消息:给「所有连接的客户端」发消息(比如群聊)
wss.clients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(`广播:有客户端说:${msg.toString()}`);
}
});
});
// 监听「客户端断开连接」事件
ws.on('close', () => {
console.log('📌 客户端断开连接');
});
// 监听「连接错误」事件
ws.on('error', (err) => {
console.error('❌ 连接异常:', err);
});
});
node server.js
此时前端连接 ws://localhost:8080 即可正常通信!
Java 开发中,SpringBoot 是绝对主流,SpringBoot 2.x 之后内置了 WebSocket 支持,无需引入额外依赖,配置极简,适合后端 Java 开发同学。
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;
@Configuration
@EnableWebSocket // 开启 WebSocket 支持
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
// 配置 WebSocket 地址:ws://localhost:8080/ws
registry.addHandler(new MyWebSocketHandler(), "/ws")
.setAllowedOrigins("*"); // 允许所有跨域(生产环境可指定域名)
}
}
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.CopyOnWriteArraySet;
public class MyWebSocketHandler extends TextWebSocketHandler {
// 存储所有连接的客户端
private static final CopyOnWriteArraySet<WebSocketSession> sessions = new CopyOnWriteArraySet<>();
// 连接成功触发
@Override
public void afterConnectionEstablished(WebSocketSession session) {
sessions.add(session);
System.out.println("✅ 客户端连接成功,当前在线数:" + sessions.size());
}
// 接收客户端消息触发
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws IOException {
String msg = message.getPayload();
System.out.println("📥 收到客户端消息:" + msg);
// 服务端给客户端发消息
session.sendMessage(new TextMessage("服务端已收到:" + msg));
}
// 连接关闭触发
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
sessions.remove(session);
System.out.println("❌ 客户端断开连接,当前在线数:" + sessions.size());
}
}
运行 SpringBoot 项目,前端连接 ws://localhost:8080/ws 即可通信。
Python 主流的 WebSocket 方案是 FastAPI(原生支持),代码极简,适合 Python 后端开发同学:
from fastapi import FastAPI, WebSocket
from fastapi.responses import HTMLResponse
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept() # 接受客户端连接
print("✅ 客户端连接成功")
while True:
data = await websocket.receive_text() # 接收客户端消息
print(f"📥 收到消息:{data}")
await websocket.send_text(f"服务端已收到:{data}") # 发送消息给客户端
运行后,前端连接 ws://localhost:8000/ws 即可通信。
这是 WebSocket 生产环境必须实现的功能,也是很多初学者学完基础用法后,遇到的最大痛点:为什么我的 WebSocket 连接会莫名断开?为什么网络恢复后连不上?
WebSocket 是持久连接,但在实际生产环境中,有很多「隐形断开」的场景:
这种断开是「静默断开」:客户端的 onclose 事件不会立刻触发,客户端以为连接还在,发消息时才发现失败,体验极差。
心跳 = 客户端和服务端之间的「定时问候」,证明对方还活着。
ping);pong);在之前的原生前端代码基础上,新增心跳和重连逻辑,无侵入,不影响原有业务:
let ws = null;
let heartBeatTimer = null; // 心跳定时器
let reconnectTimer = null; // 重连定时器
const wsUrl = 'ws://localhost:8080';
const heartBeatInterval = 10000; // 心跳间隔:10 秒
const reconnectInterval = 3000; // 重连间隔:3 秒
// 初始化 WebSocket 连接
function initWebSocket() {
ws = new WebSocket(wsUrl);
ws.onopen = function () {
console.log('✅ 连接成功');
startHeartBeat(); // 连接成功,启动心跳
ws.send('连接成功,请求登录');
};
ws.onmessage = function (event) {
const msg = event.data;
console.log('📥 收到消息:', msg);
// 收到服务端的 pong,说明心跳正常,无需处理,重置定时器即可
if (msg === 'pong') return;
// 业务消息处理...
};
ws.onclose = function () {
console.log('❌ 连接关闭,准备重连');
stopHeartBeat(); // 关闭心跳
startReconnect(); // 启动重连
};
ws.onerror = function () {
console.error('❌ 连接异常');
stopHeartBeat();
startReconnect();
};
}
// 启动心跳:定时发送 ping
function startHeartBeat() {
heartBeatTimer = setInterval(() => {
if (ws.readyState === 1) {
ws.send('ping'); // 发送心跳包
} else {
stopHeartBeat();
}
}, heartBeatInterval);
}
// 停止心跳
function stopHeartBeat() {
clearInterval(heartBeatTimer);
heartBeatTimer = null;
}
// 启动重连:失败后每隔 3 秒重试一次
function startReconnect() {
if (reconnectTimer) return; // 避免重复创建定时器
reconnectTimer = setInterval(() => {
console.log('🔄 尝试重连...');
initWebSocket(); // 重连成功后,清除重连定时器
if (ws.readyState === 1) {
clearInterval(reconnectTimer);
reconnectTimer = null;
}
}, reconnectInterval);
}
// 页面加载时初始化
window.onload = initWebSocket;
// 页面关闭时销毁
window.onunload = function () {
ws.close();
stopHeartBeat();
clearInterval(reconnectTimer);
};
✅ 服务端配合:只需要在收到 ping 消息时,回复 pong 即可,无需额外逻辑,非常简单。
这是 WebSocket 的主场,这些场景用 HTTP 实现会非常低效,甚至无法实现:
WebSocket 是「持久连接」,会占用服务端资源,以下场景用 HTTP 更合适:
答:核心 3 点:
答:客户端连接时,不受浏览器同源策略限制(可以跨域连接任意服务端),但服务端可以通过配置,限制允许的客户端域名,防止非法连接。
答:主流 2 种方式(生产必用,防止非法连接):
new WebSocket('ws://localhost:8080?token=xxx'),服务端拿到 token 验证用户身份;答:支持!ws.send() 不仅可以传字符串,还可以传 Blob、ArrayBuffer 二进制数据,适合传输图片、文件等。
new WebSocket() 创建实例,4 个事件(onopen/onmessage/onclose/onerror)+ 2 个方法(send/close);
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online