在 Web 应用早期,浏览器和服务器之间的通信几乎完全依赖 HTTP 请求。用户点击按钮,浏览器发起请求,服务器返回响应。这种模式简单、可靠,也非常适合传统页面加载和表单提交。
但随着聊天系统、在线协作、实时行情、多人游戏、消息通知等场景出现,传统 HTTP 的“一问一答”模式开始显得笨重。客户端想要知道服务器有没有新消息,就必须不断轮询服务器。轮询不仅浪费带宽,也增加服务器压力,实时性还不够理想。
WebSocket 正是在这种背景下出现的。它让浏览器和服务器之间建立一条持久的双向通信通道,使双方都可以随时主动发送消息。
一、WebSocket 是什么?
WebSocket 是一种基于 TCP 的全双工通信协议。它允许客户端和服务器在一次连接建立后,持续保持通信通道,并且双方都能主动向对方发送数据。
简单来说,HTTP 更像是:
客户端:你有新消息吗?
服务器:没有。
客户端:你有新消息吗?
服务器:没有。
客户端:你有新消息吗?
服务器:有,给你。
WebSocket 更像是:
客户端:我们建立一个长期连接吧。
服务器:可以。
服务器:有新消息了,直接发给你。
客户端:收到。
这种模式非常适合实时性要求较高的业务。
二、WebSocket 和 HTTP 的关系
WebSocket 并不是 HTTP 的替代品,而是对 HTTP 通信能力的一种补充。
WebSocket 的连接通常从一个 HTTP 请求开始。客户端会发送一个带有 Upgrade 头的 HTTP 请求,告诉服务器:“我想把这个连接升级成 WebSocket。”
示意请求如下:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: xxx
Sec-WebSocket-Version: 13
如果服务器支持 WebSocket,就会返回类似响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: xxx
状态码 101 Switching Protocols 表示协议切换成功。之后,这条连接就不再按照普通 HTTP 请求响应模式通信,而是进入 WebSocket 双向通信阶段。
三、为什么不用 HTTP 轮询?
在 WebSocket 普及之前,实时通信常见方案主要有三种:
- 短轮询
- 短轮询最简单,客户端每隔几秒请求一次服务器。但如果大多数请求都没有新数据,就会造成大量无效请求。
- 长轮询
- 长轮询比短轮询稍好。客户端发送请求后,服务器如果没有新数据,会暂时挂起请求,直到有数据或超时再返回。它减少了一些无效请求,但本质上仍然是 HTTP 请求响应模型。
- Server-Sent Events
- Server-Sent Events,简称 SSE,允许服务器持续向客户端推送事件。但它主要是单向通信:服务器推给客户端。如果客户端也要频繁发送消息,仍然需要 HTTP 请求配合。
WebSocket 的优势在于,它是真正的双向通信。客户端和服务器都可以主动发送数据,通信开销更低,实时性更好。
四、WebSocket 的核心特点
1. 全双工通信
WebSocket 最大的特点是全双工。连接建立后,客户端和服务器可以同时发送和接收消息,不需要等待对方先发起请求。这对聊天、直播弹幕、在线游戏、协作文档等场景非常重要。
2. 持久连接
WebSocket 建立后会保持连接,除非客户端、服务器或网络主动断开。相比 HTTP 每次请求都要携带大量头部信息,WebSocket 后续数据帧更轻量。
3. 低延迟
由于连接已经建立,后续通信不需要重复握手。服务器有数据时可以立刻推送给客户端,因此延迟通常比轮询方案更低。
4. 支持文本和二进制数据
WebSocket 可以传输文本,也可以传输二进制数据,比如图片片段、音视频数据、协议缓冲区数据等。
五、WebSocket 适合哪些场景?
WebSocket 不是所有业务都需要,但在以下场景中非常有价值:
- 即时聊天
- 在线客服
- 实时通知
- 协作文档
- 实时看板
- 股票、币价、体育比分等实时行情
- 多人在线游戏
- 直播间弹幕
- 在线代码协作或远程终端
判断是否需要 WebSocket,可以问一个简单问题:“服务器是否需要在客户端没有请求时,主动把数据推给客户端?”,如果答案是肯定的,WebSocket 很可能是合适方案。
六、浏览器中如何使用 WebSocket?
由于浏览器原生支持 WebSocket API,使用非常简单。
const socket = new WebSocket("wss://example.com/socket");
socket.addEventListener("open", () => {
console.log("WebSocket connected");
socket.send(JSON.stringify({ type: "hello" }));
});
socket.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
console.log("Received:", data);
});
socket.addEventListener("close", () => {
console.log("WebSocket closed");
});
socket.addEventListener("error", (error) => {
console.error("WebSocket error:", error);
});
其中:
ws://类似 HTTPwss://类似 HTTPS,表示加密 WebSocket 连接
生产环境中应优先使用 wss://,避免通信被窃听或篡改。
七、服务端简单示例
以 Node.js 的 ws 库为例:
import { WebSocketServer } from "ws";
const wss = new WebSocketServer({ port: 8080 });
wss.on("connection", (socket) => {
console.log("client connected");
socket.on("message", (message) => {
console.log("received:", message.toString());
socket.send(JSON.stringify({
type: "echo",
payload: message.toString()
}));
});
socket.on("close", () => {
console.log("client disconnected");
});
});
这个例子中,服务器接收客户端消息后,会原样返回一条 echo 消息。
真实业务中,服务端通常还需要处理身份认证、房间管理、消息广播、心跳检测、限流、断线重连等问题。
八、WebSocket 的常见问题
1. 身份认证
WebSocket 连接建立后不像普通 HTTP 请求那样每次都携带完整请求上下文,因此认证需要提前设计。
常见方式有:
- 在连接 URL 中携带短期 token
- 在 WebSocket 子协议中传递认证信息
- 连接建立后发送认证消息
- 借助 Cookie 和服务端 Session
例如:
const socket = new WebSocket("wss://example.com/socket?token=xxx");
不过 URL 中的 token 可能出现在日志里,因此更推荐使用短期、可撤销、权限有限的 token。
2. 心跳检测
网络连接可能因为代理、NAT、防火墙或移动网络切换而悄悄断开。客户端和服务器不能只依赖 close 事件判断连接是否还活着,因此通常需要心跳机制。一种常见方式是:
客户端定时发送 ping
服务器收到后返回 pong
如果连续多次没有 pong,客户端主动重连
服务端也可以主动发送 ping,用于清理已经失效的连接。
3. 断线重连
WebSocket 是长连接,但长连接并不意味着永远连接。网络抖动、服务重启、页面休眠、移动端切网都可能导致断开。客户端应该实现重连机制,并且最好使用指数退避,避免大量客户端同时重连压垮服务器。
// 示意逻辑:
let retryCount = 0;
function connect() {
const socket = new WebSocket("wss://example.com/socket");
socket.onopen = () => {
retryCount = 0;
};
socket.onclose = () => {
const delay = Math.min(1000 * 2 ** retryCount, 30000);
retryCount += 1;
setTimeout(connect, delay);
};
}
connect();
4. 消息可靠性
WebSocket 只负责传输,不天然保证业务层消息一定被正确处理。如果业务对可靠性要求高,比如订单状态、交易通知、协作文档操作,就需要设计:
- 消息 ID
- ACK 确认机制
- 消息重试
- 去重处理
- 服务端消息持久化
- 客户端断线后补拉未读消息
例如,客户端发送消息:
{
"id": "msg_123",
"type": "chat.message",
"payload": {
"text": "hello"
}
}
服务器处理后返回:
{
"type": "ack",
"id": "msg_123"
}
这样客户端才能知道这条消息是否真正被服务端接收。
5. 水平扩展
单机 WebSocket 很容易理解,但真实生产环境往往是多台服务器。问题在于:用户 A 连接在服务器 1,用户 B 连接在服务器 2。如果 A 给 B 发消息,服务器 1 需要某种方式把消息转给服务器 2。
常见解决方案包括:
- 使用 Redis Pub/Sub
- 使用 Kafka、RabbitMQ、NATS 等消息系统
- 使用统一网关管理连接
- 使用粘性会话让同一用户尽量连接到固定实例
架构可以简化理解为:
Client A -> WebSocket Server 1
Client B -> WebSocket Server 2
Server 1 -> Message Broker -> Server 2
消息系统在这里承担跨实例广播和解耦的作用。
九、WebSocket 和 SSE 怎么选?
- 如果业务只是服务器向客户端单向推送,比如通知流、日志流、状态更新,SSE 可能更简单。
- 如果业务需要客户端和服务器频繁双向通信,比如聊天、游戏、协作编辑,WebSocket 更合适。
简单对比:
| 特性 | WebSocket | SSE |
|---|---|---|
| 通信方向 | 双向 | 服务器到客户端 |
| 协议 | 独立协议,HTTP Upgrade 后切换 | 基于 HTTP |
| 浏览器支持 | 广泛支持 | 广泛支持 |
| 二进制支持 | 支持 | 不直接支持 |
| 自动重连 | 需要自己实现 | 浏览器原生支持 |
| 适合场景 | 聊天、游戏、协作 | 通知、日志、行情推送 |
十、生产环境最佳实践
使用 WebSocket 时,建议遵循这些原则:
- 生产环境使用
wss:// - 设计清晰的消息协议
- 每条重要消息带唯一 ID
- 做好认证和权限校验
- 实现心跳检测
- 实现断线重连
- 服务端清理失效连接
- 对连接数和消息频率限流
- 避免把 WebSocket 当成数据库同步工具滥用
- 对关键消息做持久化和补偿
- 多实例部署时引入消息中间件
- 监控连接数、消息延迟、断线率和错误率
一个成熟的 WebSocket 系统,难点通常不在“建立连接”,而在连接之后的治理。
十一、一个推荐的消息格式
为了让 WebSocket 通信更可维护,建议定义统一消息结构:
{
"id": "msg_001",
"type": "chat.message",
"timestamp": 1710000000000,
"payload": {
"roomId": "room_1",
"content": "Hello WebSocket"
}
}
其中:
id用于追踪和去重type用于区分消息类型timestamp用于排序和调试payload存放业务数据
错误消息也应标准化:
{
"type": "error",
"code": "UNAUTHORIZED",
"message": "Authentication failed"
}
这样前后端就能围绕协议协作,而不是在大量零散字段中互相猜测。
十二、总结
WebSocket 解决的是 Web 应用中的实时双向通信问题。它通过一次握手建立持久连接,让客户端和服务器可以低延迟、低开销地互相发送消息。
它非常适合聊天、协作、游戏、实时看板和行情推送等场景。但 WebSocket 不是简单地“连上就完事”。真正的工程重点在于认证、心跳、重连、消息可靠性、水平扩展和监控治理。
如果只是单向推送,SSE 可能更轻量;如果需要频繁双向通信,WebSocket 通常是更好的选择。
WebSocket 让 Web 从“客户端主动询问”走向了“服务端实时对话”,但要把它用好,需要的不只是协议能力,更是完整的实时系统设计。
喜欢的话,留下你的评论吧~