在早期的 Web 开发中,网页上的数据几乎都是“静止”的。你想看最新的球赛比分、股票行情或者聊天消息,必须手动刷新一下网页。
如今,聊天软件、多人联机游戏、实时股票看板等应用在网页端已经随处可见。它们能像本地客户端一样,毫秒级地实时推送数据。这一切的幕后功臣,就是 WebSocket。
这里用通俗易懂的语言,带你搞明白 WebSocket 的前世今生、工作原理以及开发实践。
在了解 WebSocket 之前,我们先来看看如果没有它,我们该怎么实现“实时数据更新”。
HTTP 协议是一个请求-响应模型。这意味着:
为了在网页上实现“实时推送”,开发者们曾想出了几种妥协手段:
| 方案 | 拟人化比喻 | 优缺点 |
|---|---|---|
| 短轮询 (Short Polling) | 每隔 2 秒给餐馆打个电话: “我外卖好了吗?” “没好,挂了。” 又过 2 秒:“我外卖好了吗?” | 优点:实现极简单。 缺点:服务器压力极大,充斥着无用连接,延迟高。 |
| 长轮询 (Long Polling) | 给餐馆打电话:“外卖好了告诉我,没好别挂电话。” 餐馆让电话通着,直到外卖好了才说话。 挂断后客户端立刻再拨过去。 | 优点:比短轮询延迟低。 缺点:依然需要频繁建立连接,服务器需要挂起大量线程,极其消耗资源。 |
因此,WebSocket 应运而生。它打破了 HTTP 单向通信的限制,建立了一条全双工、长连接、持久化的通信专线。
WebSocket 是一种在单个 TCP 连接上进行全双工Full-Duplex通信的协议。它在 2011 年被 IETF 定为标准(RFC 6455)。
什么是全双工(Full-Duplex)?
WebSocket 并不是凭空产生的,它在建立连接时非常聪明地借用了 HTTP 的肩膀。这个过程被称为握手(Handshake)。
HTTP 101 Switching Protocols。| 客户端请求 (HTTP Upgrade) | 服务端响应 (101 Switching) |
|---|---|
GET /chat HTTP/1.1 | HTTP/1.1 101 Switching Protocols |
Host: example.com | Upgrade: websocket |
Upgrade: websocket(表明想要升级协议) | Connection: Upgrade(同意升级协议) |
Connection: Upgrade | Sec-WebSocket-Accept: HSmrc0sMlYJ... |
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== |
握手成功后,传输的数据将不再被包裹在厚重的 HTTP 报文里,而是使用 WebSocket 独有的轻量级数据帧(Frame)。每个数据帧的头部开销仅为 2 到 10 字节,相比 HTTP 动辄数 KB 的 Header,开销近乎为零!
光说不练假把式,我们用几行代码在本地搭建一个最基础的 WebSocket 服务。
ws 库)首先,在项目里安装包:
npm install ws
创建 server.js 并写入:
const { WebSocketServer } = require('ws');
// 在 8080 端口启动 WebSocket 服务
const wss = new WebSocketServer({ port: 8080 });
console.log('WebSocket server is running on ws://localhost:8080');
wss.on('connection', (ws) => {
console.log('有新的客户端连接成功!');
// 监听客户端发来的消息
ws.on('message', (message) => {
console.log(`收到客户端消息: ${message}`);
// 向客户端回送一条确认消息
ws.send(`服务器已收到你的消息,内容是:"${message}"`);
});
// 监听连接断开
ws.on('close', () => {
console.log('客户端连接断开');
});
});
在 HTML 或浏览器控制台(F12)中运行以下代码:
// 1. 创建 WebSocket 实例,连接到我们刚刚启动的服务器
const socket = new WebSocket('ws://localhost:8080');
// 2. 监听连接打开事件
socket.onopen = (event) => {
console.log('成功连接到服务器!正在发送测试数据...');
// 3. 发送数据给服务器
socket.send('哈喽,我是浏览器!');
};
// 4. 监听接收服务器消息事件
socket.onmessage = (event) => {
console.log(`收到服务器的回复: ${event.data}`);
};
// 5. 监听关闭连接事件
socket.onclose = (event) => {
console.log('连接已关闭');
};
在写 Demo 时,WebSocket 看起来非常完美。但如果你要把它推向千万用户的生产环境,必须要面对以下三大挑战:
最佳实践一:心跳检测机制(Heartbeat / Ping-Pong)
在实际网络环境中,防火墙、网关、运营商路由等节点如果发现一个 TCP 连接长时间没有数据传输,会自作主张地将其切断(称之为“死连接”)。
解决办法:客户端和服务器需要约定一个“心跳”机制。例如客户端每隔 15 秒向服务器发送一个轻量级的 Ping 包,服务器收到后回一个 Pong 包。如果在 30 秒内没有任何响应,说明连接已死,客户端应立刻发起重连。
最佳实践二:多服务器集群下的消息分发(Redis Pub/Sub)
当单台服务器装不下你的在线用户时,你会部署多台服务器。但问题来了:
解决办法:引入一个中间层(如 Redis 的发布/订阅订阅模式,或者是 RabbitMQ/Kafka)。服务器 1 收到消息后,将其“发布”到 Redis 频道,所有服务器共同订阅这个频道。哪个服务器拥有用户 B 的连接,哪个服务器负责把消息送达。
最佳实践三:强制使用 WSS(Secure WebSocket)
类似于传统的 http:// 升级为 https://,WebSocket 也有加密安全版 wss://。
在生产环境中,请一律使用 wss://。因为除了数据加密防窃听外,许多中间代理服务器(如部分旧路由器)不认识普通的 ws:// 协议,会直接拦截该流量;而 wss:// 数据在 TLS 加密通道中传输,中间节点只能将其作为常规加密流量转发,从而大大提高了连接成功率。
WebSocket 的出现并不是为了完全取代 HTTP,而是对 HTTP 无法高效处理实时数据的有力补充。
暂无评论,快来抢沙发吧!