💡 前言
在传统的 Web 应用中,浏览器和服务器之间的交流通常是这样的:
- 浏览器:"服务器,有新消息吗?"
- 服务器:"没有。"
- (过 1 秒)
- 浏览器:"服务器,现在有新消息吗?"
- 服务器:"还是没有。"
这种模式叫轮询(Polling),效率极低且浪费资源。
如果有一种技术,能让服务器在有新消息时主动推送到浏览器,那该多好?
WebSocket 就是为此而生的。今天,我们就来看看它是如何改变 Web 通信规则的。
1. 核心概念:全双工通信 🤝
WebSocket 是一种在单个 TCP 连接上进行 全双工(Full-Duplex) 通信的协议。
- 全双工 :意思是双方可以同时发送和接收数据,互不干扰。
- 持久连接:一旦建立连接,就会一直保持打开状态,直到某一方主动关闭。
📖 生活类比:短信 vs 电话
| 特性 | HTTP (传统 Web) | WebSocket |
|---|---|---|
| 类比 | 发短信 / 写信 📩 | 打电话 📞 |
| 连接方式 | 短连接。发一条消息建一次连接,说完就挂断。 | 长连接。拨通后一直在线,随时可以说话。 |
| 主动性 | 只有客户端能发起请求,服务器只能被动回复。 | 双方都可以主动发送消息。 |
| 开销 | 每次都要带上完整的 HTTP 头(Cookie, User-Agent等),头部很大。 | 第一次握手后,后续数据传输头部极小,非常轻量。 |
| 实时性 | 差。需要不断轮询才能知道有没有新消息。 | 极高。服务器有消息立刻推送。 |
2. 为什么 HTTP 不够用? 🤔
在 WebSocket 出现之前,为了实现"实时效果",开发者们想尽了办法:
-
短轮询 (Short Polling):
- 每隔几秒发一个 HTTP 请求问服务器。
- 缺点:大部分请求都是无效的(服务器说"没数据"),浪费带宽和服务器资源。
-
长轮询 (Long Polling):
- 客户端发请求,服务器如果有数据就立即返回;如果没有,就按住请求不放,直到有数据或超时才返回。
- 缺点:服务器维持大量挂起的连接,压力大;延迟依然比 WebSocket 高。
-
iframe 流:
- 在页面隐藏一个 iframe,长连接获取数据。
- 缺点:实现复杂,兼容性差,维护噩梦。
WebSocket 的出现,终结了这些"黑科技",提供了标准的、高效的实时通信方案。
3. WebSocket 的工作原理 ⚙️
WebSocket 的建立过程非常巧妙,它借用 了 HTTP 协议来完成"握手",然后切换到 WebSocket 协议进行通信。
第一阶段:握手 (Handshake) 🤝
-
客户端发起 :浏览器发送一个特殊的 HTTP 请求,头里包含
Upgrade: websocket。httpGET /chat HTTP/1.1 Host: example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Sec-WebSocket-Version: 13 -
服务器回应 :服务器如果支持 WebSocket,会返回
101 Switching Protocols状态码,表示"好嘞,我们要切换协议了"。httpHTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
第二阶段:数据传输 (Data Transfer) 📦
- 握手成功后,HTTP 连接升级为 WebSocket 连接。
- 后续的通信不再使用 HTTP 格式,而是使用 WebSocket 自己的 帧(Frame) 格式。
- 特点:头部非常小(最少只有 2 字节),非常适合频繁的小数据包传输。
第三阶段:关闭连接 (Closing) 👋
- 任何一方都可以发送关闭帧,另一方确认后,连接断开。
4. 代码示例:简单上手 💻
前端 (JavaScript)
javascript
// 1. 创建连接
const socket = new WebSocket("ws://example.com/chat");
// 2. 监听连接打开
socket.onopen = function (event) {
console.log("连接已建立");
// 发送消息
socket.send("Hello Server!");
};
// 3. 监听收到消息
socket.onmessage = function (event) {
console.log("收到消息:", event.data);
};
// 4. 监听错误
socket.onerror = function (error) {
console.error("发生错误:", error);
};
// 5. 监听连接关闭
socket.onclose = function (event) {
console.log("连接已关闭");
};
后端 (Node.js + ws 库)
javascript
const WebSocket = require("ws");
const wss = new WebSocket.Server({ port: 8080 });
wss.on("connection", function connection(ws) {
console.log("客户端已连接");
// 监听收到的消息
ws.on("message", function incoming(message) {
console.log("收到:", message);
// 广播给所有客户端
wss.clients.forEach(function each(client) {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
ws.send("Hello Client!");
});
5. 适用场景:什么时候用 WebSocket? 🎯
并不是所有场景都需要 WebSocket,请根据需求选择:
| 场景 | 推荐技术 | 原因 |
|---|---|---|
| 即时聊天 (IM) | ✅ WebSocket | 需要双向、低延迟、高频消息。 |
| 在线游戏 | ✅ WebSocket | 需要毫秒级同步玩家位置、状态。 |
| 股票/行情推送 | ✅ WebSocket | 数据变化快,需要实时推送到前端。 |
| 协同编辑 | ✅ WebSocket | 多人同时修改文档,需实时同步光标和内容。 |
| 直播弹幕 | ✅ WebSocket | 高并发、实时性要求高。 |
| 普通网页浏览 | ❌ HTTP | 一次性加载,无需保持连接。 |
| 表单提交 | ❌ HTTP | 偶尔发生的写操作,HTTP 足够。 |
| RESTful API | ❌ HTTP | 标准的 CRUD 操作,HTTP 语义更清晰。 |
6. WebSocket vs HTTP/2 vs SSE 🆚
除了 HTTP/1.1,还有其他技术也能实现类似功能,怎么选?
-
SSE (Server-Sent Events):
- 特点:单向通信(服务器 -> 客户端)。基于 HTTP。
- 适用:只需要服务器推送,不需要客户端频繁发送数据的场景(如新闻推送、股价更新)。
- 优势:实现简单,原生支持重连。
-
HTTP/2:
- 特点:多路复用,头部压缩。
- 注意:HTTP/2 依然是"请求-响应"模型,虽然性能好,但本质上不是全双工推送。不过,HTTP/2 使得长轮询的性能大大提升,可以作为 WebSocket 的备选。
-
WebSocket:
- 特点:双向、全双工、低延迟。
- 适用:复杂的交互场景(聊天、游戏)。
💡 选型建议:
- 如果是单向推送 ,优先选 SSE(简单、稳定)。
- 如果是双向交互 ,必须选 WebSocket。
7. 常见坑与注意事项 ⚠️
-
心跳检测 (Heartbeat):
- 网络中间设备(如防火墙、Nginx)可能会长时间无数据就切断连接。
- 解决 :客户端和服务端定期互相发送
ping/pong消息,保持连接活跃。
-
断线重连:
- WebSocket 连接可能因网络波动断开。
- 解决:前端需要实现指数退避重连机制(第一次 1s 后重连,第二次 2s,第三次 4s...),避免瞬间大量重连冲击服务器。
-
安全性:
- 使用
wss://(WebSocket Secure) 而不是ws://,类似于 HTTP 和 HTTPS。 - 握手阶段依然可以进行身份验证(如通过 Cookie 或 Token)。
- 使用
-
负载均衡:
- WebSocket 是有状态的长连接。如果后端有多台服务器,需要确保同一用户的连接始终指向同一台服务器(IP Hash 或 Sticky Session),或者使用 Redis 等共享存储来同步消息。
8. 总结记忆口诀 🧠
HTTP 像短信,一问一答慢半拍。
WS 像电话,打通之后随便聊。
握手借 HTTP,升级协议换通道。
双向通信低延迟,聊天游戏少不了。
记得心跳保连接,断线重连不能少。
希望这篇文档能帮你彻底理解 WebSocket!如果觉得有用,欢迎点赞收藏~ 🌟