WebSocket协议详解
约 1589 字大约 5 分钟
websocketprotocol
2025-07-04
概述
WebSocket是一种在单个TCP连接上提供全双工通信的协议(RFC 6455)。与HTTP的请求-响应模式不同,WebSocket允许服务端主动向客户端推送数据,非常适合实时通信场景如聊天、在线游戏、实时数据推送等。
WebSocket握手(Upgrade)
WebSocket连接通过HTTP Upgrade机制建立,从HTTP升级为WebSocket协议。
握手请求详解:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Extensions: permessage-deflate
Origin: http://example.comSec-WebSocket-Accept计算:
import base64
import hashlib
key = "dGhlIHNhbXBsZSBub25jZQ=="
magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
accept = base64.b64encode(
hashlib.sha1((key + magic).encode()).digest()
).decode()
# 结果: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=这个机制不是为了安全性,而是为了确认服务端确实理解WebSocket协议(而不是一个碰巧返回200的HTTP服务器)。
帧格式(Frame Format)
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - -+
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - -+-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - -+
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+关键字段说明:
| 字段 | 说明 |
|---|---|
| FIN | 1=最后一个分片,0=后续还有分片 |
| RSV1-3 | 保留位,扩展使用(如压缩) |
| Opcode | 帧类型标识 |
| MASK | 1=有掩码(客户端发的帧必须设为1) |
| Payload length | 负载长度:0-125直接表示,126用2字节扩展,127用8字节扩展 |
| Masking-key | 4字节掩码密钥 |
帧类型(Opcode)
控制帧详解
Close帧(0x8):
关闭握手流程:
1. 一方发送Close帧(可选包含状态码和原因)
2. 另一方回复Close帧
3. 底层TCP连接关闭
常见状态码:
1000 - 正常关闭
1001 - 端点离开(如页面关闭)
1002 - 协议错误
1003 - 不支持的数据类型
1006 - 异常关闭(未发送Close帧)
1008 - 违反策略
1009 - 消息过大
1011 - 服务端意外错误Ping/Pong帧(0x9/0xA):
// 服务端发送Ping
ws.ping(Buffer.from('heartbeat'));
// 客户端自动回复Pong(浏览器自动处理)
// 服务端也可以监听Pong
ws.on('pong', (data) => {
console.log('收到Pong响应', data.toString());
});掩码(Masking)
客户端发送的每个帧必须使用掩码,这是为了防止恶意的WebSocket客户端对缓存代理进行投毒攻击。
掩码算法:
masked_data[i] = original_data[i] XOR masking_key[i % 4]
示例:
masking_key = [0x37, 0xfa, 0x21, 0x3d]
original = "Hello" = [0x48, 0x65, 0x6c, 0x6c, 0x6f]
masked = [0x48^0x37, 0x65^0xfa, 0x6c^0x21, 0x6c^0x3d, 0x6f^0x37]
= [0x7f, 0x9f, 0x4d, 0x51, 0x58]子协议(Sub-protocols)
WebSocket允许在握手时协商子协议,用于定义消息的格式和语义:
// 客户端指定支持的子协议
const ws = new WebSocket('wss://example.com/chat', ['graphql-ws', 'json']);
// 服务端选择一个子协议
// 响应头: Sec-WebSocket-Protocol: graphql-ws常见子协议:
graphql-ws/graphql-transport-ws:GraphQL订阅mqtt:MQTT over WebSocketstomp:STOMP消息协议wamp:Web Application Messaging Protocol
扩展(Extensions)
permessage-deflate压缩
对每条消息进行zlib压缩,显著减少传输数据量:
# 握手时协商
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits=15
# 服务端确认
Sec-WebSocket-Extensions: permessage-deflate; server_max_window_bits=15// Node.js ws库配置
const WebSocket = require('ws');
const wss = new WebSocket.Server({
port: 8080,
perMessageDeflate: {
zlibDeflateOptions: {
chunkSize: 1024,
memLevel: 7,
level: 3
},
zlibInflateOptions: {
chunkSize: 10 * 1024
},
clientNoContextTakeover: true,
serverNoContextTakeover: true,
serverMaxWindowBits: 10,
concurrencyLimit: 10,
threshold: 1024 // 小于1KB的消息不压缩
}
});心跳机制
WebSocket连接可能被中间代理或防火墙因空闲超时而断开,心跳机制保持连接活跃:
// 服务端心跳实现(Node.js)
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
function heartbeat() {
this.isAlive = true;
}
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
// 每30秒检测一次
const interval = setInterval(() => {
wss.clients.forEach((ws) => {
if (ws.isAlive === false) {
return ws.terminate(); // 无响应,断开连接
}
ws.isAlive = false;
ws.ping(); // 发送Ping
});
}, 30000);
wss.on('close', () => {
clearInterval(interval);
});// 客户端重连机制
class ReconnectingWebSocket {
constructor(url, options = {}) {
this.url = url;
this.reconnectInterval = options.reconnectInterval || 3000;
this.maxRetries = options.maxRetries || 10;
this.retries = 0;
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
this.retries = 0;
console.log('WebSocket connected');
};
this.ws.onclose = (event) => {
if (!event.wasClean && this.retries < this.maxRetries) {
this.retries++;
const delay = this.reconnectInterval * Math.pow(1.5, this.retries);
setTimeout(() => this.connect(), delay);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
}
}水平扩展(Scaling)
单机WebSocket连接数受限,水平扩展需要解决两个问题:会话亲和性和跨节点消息广播。
Sticky Session(Nginx配置):
upstream websocket_servers {
ip_hash; # 基于IP的会话亲和
server ws1.internal:8080;
server ws2.internal:8080;
server ws3.internal:8080;
}
server {
listen 80;
location /ws {
proxy_pass http://websocket_servers;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 86400s; # 防止空闲超时
proxy_send_timeout 86400s;
}
}Redis Pub/Sub跨节点广播:
const Redis = require('ioredis');
const pub = new Redis();
const sub = new Redis();
// 订阅频道
sub.subscribe('chat:room:1');
sub.on('message', (channel, message) => {
// 广播给当前节点的所有连接
const data = JSON.parse(message);
localClients.forEach(client => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
});
// 收到客户端消息后发布
wss.on('connection', (ws) => {
ws.on('message', (data) => {
pub.publish('chat:room:1', data);
});
});WebSocket vs SSE vs Long Polling
| 特性 | WebSocket | SSE | Long Polling |
|---|---|---|---|
| 通信方向 | 全双工 | 服务端→客户端 | 模拟双向 |
| 协议 | ws/wss | HTTP | HTTP |
| 自动重连 | 需手动实现 | 浏览器内置 | 需手动实现 |
| 二进制支持 | 支持 | 不支持 | 支持 |
| 连接开销 | 低(一次握手) | 低 | 高(频繁HTTP请求) |
| 适用场景 | 实时双向通信 | 单向推送 | 兼容性要求高 |
总结
WebSocket协议通过HTTP Upgrade机制建立全双工连接,帧格式紧凑高效,支持文本和二进制数据。在生产环境中,需要关注心跳保活、断线重连、消息压缩和水平扩展等问题。对于只需要单向推送的场景,SSE可能是更简单的选择。
贡献者
更新日志
2026/3/14 13:09
查看所有更新日志
9f6c2-feat: organize wiki content and refresh site setup于