WebSocket 速查表
本文提供 WebSocket 开发的快速参考,包括常用 API、代码片段和配置选项。建议收藏本页,在开发过程中快速查阅。
协议规范
| 项目 | 说明 |
|---|---|
| 协议标准 | RFC 6455 |
| WebSocket over HTTP/2 | RFC 8441 |
| 默认端口 | ws:// 80, wss:// 443 |
| URL 格式 | ws://host:port/path?query |
| 握手方法 | HTTP GET + Upgrade 头 (HTTP/1.1) / CONNECT + :protocol 头 (HTTP/2) |
| 消息类型 | 文本(UTF-8)、二进制 |
| 最大消息大小 | 理论无限制(实际受服务器配置限制) |
RFC 8441 (WebSocket over HTTP/2)
RFC 8441 允许 WebSocket 在 HTTP/2 连接上运行,实现连接复用:
// HTTP/2 环境下的 WebSocket 握手
:method = CONNECT
:protocol = websocket
:scheme = https
:path = /chat
:authority = server.example.com
浏览器支持:Chrome 63+、Firefox 等现代浏览器自动支持
优势:
- WebSocket 与 HTTP/2 共享同一 TCP 连接
- 多个 WebSocket 可在同一 HTTP/2 连接上并行运行
- 减少连接建立延迟
浏览器 API
创建连接
// 基本连接
const socket = new WebSocket('ws://localhost:8080');
// 安全连接
const secureSocket = new WebSocket('wss://example.com/chat');
// 指定子协议
const socketWithProtocol = new WebSocket('ws://localhost:8080', 'chat-protocol');
// 多个子协议(按优先级排序)
const socketWithProtocols = new WebSocket('ws://localhost:8080', ['chat', 'json']);
// 动态选择协议
const protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
const socket = new WebSocket(`${protocol}//${location.host}/ws`);
连接状态
| 状态 | 值 | 说明 |
|---|---|---|
| CONNECTING | 0 | 连接中,握手进行中 |
| OPEN | 1 | 已连接,可以通信 |
| CLOSING | 2 | 关闭中,正在执行关闭握手 |
| CLOSED | 3 | 已关闭,无法通信 |
// 状态检查
if (socket.readyState === WebSocket.OPEN) {
socket.send('Hello');
}
// 使用常量
if (socket.readyState === WebSocket.CONNECTING) {
console.log('连接中...');
}
事件处理
socket.onopen = (event) => {
console.log('连接已建立');
};
socket.onmessage = (event) => {
console.log('收到消息:', event.data);
};
socket.onerror = (event) => {
console.error('连接错误');
};
socket.onclose = (event) => {
console.log('连接关闭:', event.code, event.reason);
};
发送消息
socket.send('文本消息');
socket.send(JSON.stringify({ type: 'chat', content: 'Hello' }));
socket.send(new ArrayBuffer(10));
socket.send(new Blob(['blob data']));
socket.send(new Uint8Array([1, 2, 3, 4]));
实例属性
| 属性 | 类型 | 说明 |
|---|---|---|
url | string | 只读,连接的 URL |
readyState | number | 只读,连接状态 |
protocol | string | 只读,服务器选定的子协议 |
extensions | string | 只读,服务器选定的扩展 |
bufferedAmount | number | 只读,已排队未发送的字节数 |
binaryType | string | 二进制数据格式,'blob' 或 'arraybuffer' |
关闭连接
socket.close();
socket.close(1000, '正常关闭');
socket.close(3000, '自定义原因');
关闭码(完整列表)
标准关闭码(RFC 6455 定义):
| 代码 | 名称 | 说明 | 是否可发送 |
|---|---|---|---|
| 1000 | Normal Closure | 正常关闭,连接目的已达成 | 是 |
| 1001 | Going Away | 端点离开(如页面关闭、服务器关闭) | 是 |
| 1002 | Protocol Error | 协议错误,收到不符合规范的数据 | 是 |
| 1003 | Unsupported Data | 不支持的数据类型(如收到二进制但只支持文本) | 是 |
| 1004 | Reserved | 保留,未来可能定义 | - |
| 1005 | No Status Rcvd | 未收到状态码(保留,不应发送) | 否 |
| 1006 | Abnormal Closure | 异常关闭,未收到关闭帧(保留,不应发送) | 否 |
| 1007 | Invalid Frame Payload | 无效的负载数据,消息格式错误 | 是 |
| 1008 | Policy Violation | 策略违规,通用错误码 | 是 |
| 1009 | Message Too Big | 消息过大,超出服务器处理能力 | 是 |
| 1010 | Mandatory Ext | 缺少必要的扩展 | 是 |
| 1011 | Internal Error | 服务器内部错误,无法完成请求 | 是 |
| 1012 | Service Restart | 服务重启,稍后可重连 | 是 |
| 1013 | Try Again Later | 服务器繁忙,稍后重试 | 是 |
| 1014 | Bad Gateway | 网关或代理服务器错误 | 是 |
| 1015 | TLS Handshake | TLS 握手失败(保留,不应发送) | 否 |
扩展关闭码(IANA 注册):
| 代码范围 | 用途 | 说明 |
|---|---|---|
| 3000-3999 | 库/框架使用 | 由 WebSocket 库或框架定义 |
| 4000-4999 | 应用自定义 | 由应用程序自行定义 |
常用自定义关闭码示例:
| 代码 | 名称 | 说明 |
|---|---|---|
| 4000 | User Logout | 用户主动登出 |
| 4001 | Session Expired | 会话过期 |
| 4002 | Reconnect Required | 需要重新连接 |
| 4003 | Rate Limited | 请求频率超限 |
重要提示:
- 1005、1006、1015 是保留码,不应主动发送,仅由浏览器在特定情况下设置
- 自定义关闭码应使用 4000-4999 范围,避免与标准码冲突
- 关闭原因最大 123 字节(UTF-8 编码)
Node.js (ws 库)
安装
npm install ws
基本服务器
const WebSocket = require('ws');
const wss = new WebSocket.Server({ port: 8080 });
wss.on('connection', (ws) => {
ws.on('message', (data) => {
ws.send('Echo: ' + data);
});
});
配置选项
const wss = new WebSocket.Server({
port: 8080,
host: '0.0.0.0',
maxPayload: 100 * 1024 * 1024,
clientTracking: true,
perMessageDeflate: {
zlibDeflateOptions: { level: 3 }
}
});
验证客户端
const wss = new WebSocket.Server({
port: 8080,
verifyClient: (info, callback) => {
const origin = info.origin;
if (origin !== 'http://localhost:3000') {
callback(false, 403, 'Forbidden');
return;
}
callback(true);
}
});
广播消息
function broadcast(wss, message) {
wss.clients.forEach((client) => {
if (client.readyState === WebSocket.OPEN) {
client.send(message);
}
});
}
心跳检测
function heartbeat() {
this.isAlive = true;
}
wss.on('connection', (ws) => {
ws.isAlive = true;
ws.on('pong', heartbeat);
});
setInterval(() => {
wss.clients.forEach((ws) => {
if (!ws.isAlive) return ws.terminate();
ws.isAlive = false;
ws.ping();
});
}, 30000);
Python (websockets)
安装
pip install websockets
基本服务器
import asyncio
import websockets
async def handler(websocket):
async for message in websocket:
await websocket.send(f"Echo: {message}")
async def main():
async with websockets.serve(handler, "localhost", 8080):
await asyncio.Future()
asyncio.run(main())
发送消息
await websocket.send("文本消息")
await websocket.send(json.dumps({"type": "chat"}))
await websocket.send(b"二进制数据")
接收消息
message = await websocket.recv()
async for message in websocket:
print(message)
关闭连接
await websocket.close(1000, "正常关闭")
Python (FastAPI)
基本用法
from fastapi import FastAPI, WebSocket
app = FastAPI()
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
await websocket.accept()
while True:
data = await websocket.receive_text()
await websocket.send_text(f"Echo: {data}")
消息类型
text = await websocket.receive_text()
data = await websocket.receive_bytes()
json_data = await websocket.receive_json()
await websocket.send_text("text")
await websocket.send_bytes(b"bytes")
await websocket.send_json({"key": "value"})
Go (gorilla/websocket)
安装
go get github.com/gorilla/websocket
基本服务器
package main
import (
"log"
"net/http"
"github.com/gorilla/websocket"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true },
}
func handleWebSocket(w http.ResponseWriter, r *http.Request) {
conn, _ := upgrader.Upgrade(w, r, nil)
defer conn.Close()
for {
_, msg, _ := conn.ReadMessage()
conn.WriteMessage(websocket.TextMessage, msg)
}
}
func main() {
http.HandleFunc("/ws", handleWebSocket)
log.Fatal(http.ListenAndServe(":8080", nil))
}
读写消息
messageType, message, err := conn.ReadMessage()
err = conn.WriteMessage(websocket.TextMessage, []byte("text"))
err = conn.WriteJSON(map[string]string{"key": "value"})
var data map[string]string
err = conn.ReadJSON(&data)
关闭连接
err := conn.WriteMessage(websocket.CloseMessage,
websocket.FormatCloseMessage(1000, "正常关闭"))
conn.Close()
Nginx 配置
upstream websocket {
server 127.0.0.1:8080;
}
server {
listen 80;
server_name example.com;
location /ws {
proxy_pass http://websocket;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400;
}
}
常用代码片段
自动重连客户端
class ReconnectingWebSocket {
constructor(url) {
this.url = url;
this.socket = null;
this.reconnectAttempts = 0;
this.connect();
}
connect() {
this.socket = new WebSocket(this.url);
this.socket.onopen = () => this.reconnectAttempts = 0;
this.socket.onclose = () => {
if (this.reconnectAttempts++ < 5) {
setTimeout(() => this.connect(), 1000 * this.reconnectAttempts);
}
};
}
send(data) {
if (this.socket?.readyState === WebSocket.OPEN) {
this.socket.send(data);
}
}
}
连接管理器
class ConnectionManager {
constructor() {
this.connections = new Map();
}
add(id, ws) {
this.connections.set(id, ws);
}
remove(id) {
this.connections.delete(id);
}
broadcast(message, exclude = null) {
const data = JSON.stringify(message);
this.connections.forEach((ws, id) => {
if (id !== exclude && ws.readyState === WebSocket.OPEN) {
ws.send(data);
}
});
}
}
心跳管理
class Heartbeat {
constructor(socket, interval = 30000) {
this.socket = socket;
this.interval = interval;
this.missed = 0;
this.maxMissed = 3;
}
start() {
this.timer = setInterval(() => {
if (++this.missed > this.maxMissed) {
this.socket.close();
return;
}
this.socket.send(JSON.stringify({ type: 'ping' }));
}, this.interval);
this.socket.on('message', (data) => {
const msg = JSON.parse(data);
if (msg.type === 'pong') this.missed = 0;
});
}
stop() {
clearInterval(this.timer);
}
}
消息队列
class MessageQueue {
constructor(maxSize = 100) {
this.queue = [];
this.maxSize = maxSize;
}
add(message) {
if (this.queue.length >= this.maxSize) {
this.queue.shift();
}
this.queue.push(message);
}
flush(send) {
while (this.queue.length) {
send(this.queue.shift());
}
}
}
调试技巧
Chrome DevTools
- 打开开发者工具 (F12)
- 切换到 Network 标签
- 筛选 WS (WebSocket)
- 点击连接查看消息
日志包装
function createLoggedSocket(url) {
const socket = new WebSocket(url);
const originalSend = socket.send.bind(socket);
socket.send = (data) => {
console.log('→', data);
return originalSend(data);
};
socket.addEventListener('message', (e) => {
console.log('←', e.data);
});
return socket;
}
常见问题
Q: 连接立即断开
检查:
- URL 是否正确
- 服务器是否运行
- Origin 是否被允许
- 是否需要认证
Q: 消息发送失败
检查:
readyState是否为 OPEN- 消息格式是否正确
- 是否有速率限制
Q: 连接超时
解决:
- 实现心跳机制
- 增加 Nginx
proxy_read_timeout - 检查网络稳定性
Q: 跨域问题
解决:
- 服务器配置
CheckOrigin - 使用正确的 Origin
- 使用 wss:// 协议
数据帧格式
帧结构
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| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
操作码
| Opcode | 含义 |
|---|---|
| 0x0 | 继续帧 |
| 0x1 | 文本帧 |
| 0x2 | 二进制帧 |
| 0x8 | 关闭帧 |
| 0x9 | Ping 帧 |
| 0xA | Pong 帧 |
握手头部
客户端请求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
服务端响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Accept 计算
import base64
import hashlib
def compute_accept(key):
GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
sha1 = hashlib.sha1((key + GUID).encode()).digest()
return base64.b64encode(sha1).decode()
const crypto = require('crypto');
function computeAccept(key) {
const GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
return crypto.createHash('sha1').update(key + GUID).digest('base64');
}
命令行工具
wscat
# 安装
npm install -g wscat
# 连接服务器
wscat -c ws://localhost:8080
# 带子协议连接
wscat -c ws://localhost:8080 -p chat
# 发送消息
> {"type": "ping"}
# 指定头部
wscat -c ws://localhost:8080 -H "Authorization: Bearer token"
# 监听模式(作为服务器)
wscat -l 8080
websocat
# 安装(需要 Rust)
cargo install websocat
# 基本连接
websocat ws://localhost:8080
# 带重连
websocat -E ws://localhost:8080
# 广播服务器
websocat -E -t ws-listen:8080:fork broadcast:mirror:
curl(仅握手测试)
# 测试握手
curl -i -N \
-H "Connection: Upgrade" \
-H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
-H "Sec-WebSocket-Version: 13" \
http://localhost:8080/ws
浏览器兼容性
| 浏览器 | 最低版本 | 备注 |
|---|---|---|
| Chrome | 4+ | 完全支持 |
| Firefox | 4+ | 完全支持 |
| Safari | 5+ | 完全支持 |
| Edge | 12+ | 完全支持 |
| Opera | 11+ | 完全支持 |
| IE | 10+ | 部分支持 |
特性检测:
if ('WebSocket' in window) {
// 支持 WebSocket
} else {
// 降级到轮询
}
相关链接
| 资源 | 链接 |
|---|---|
| RFC 6455 | https://tools.ietf.org/html/rfc6455 |
| MDN WebSocket API | https://developer.mozilla.org/en-US/docs/Web/API/WebSocket |
| IANA WebSocket 注册表 | https://www.iana.org/assignments/websocket/websocket.xhtml |
| ws 库文档 | https://github.com/websockets/ws |
| Socket.IO 文档 | https://socket.io/docs/ |
| gorilla/websocket | https://github.com/gorilla/websocket |
| Python websockets | https://websockets.readthedocs.io/ |
WebSocketStream API 速查
浏览器支持
| 浏览器 | 支持版本 |
|---|---|
| Chrome | 124+ |
| Edge | 124+ |
| Firefox | 尚未支持 |
| Safari | 尚未支持 |
基本用法
// 检测支持
if ('WebSocketStream' in self) {
const wss = new WebSocketStream('wss://example.com/ws');
// 等待连接
const { readable, writable, protocol, extensions } = await wss.opened;
// 读取消息
const reader = readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
console.log('收到:', value);
}
// 发送消息
const writer = writable.getWriter();
await writer.write('Hello');
writer.releaseLock();
// 关闭连接
wss.close();
const { code, reason } = await wss.closed;
}
与传统 WebSocket 对比
| 特性 | WebSocket | WebSocketStream |
|---|---|---|
| API 风格 | 基于事件 | 基于 Promise 和 Stream |
| 背压支持 | 无 | 自动支持 |
| 消息处理 | 回调函数 | async/await |
| 浏览器支持 | 99%+ | Chrome/Edge 124+ |
背压机制说明
WebSocketStream 的核心优势是自动处理背压。当消费者处理消息的速度跟不上生产者发送消息的速度时,系统会自动调节:
async function processWithBackpressure(wss) {
const { readable } = await wss.opened;
const reader = readable.getReader();
while (true) {
const { value, done } = await reader.read();
if (done) break;
// 处理消息可能需要较长时间
// 由于使用 Stream API,读取会等待处理完成
// 不会一次性加载所有消息到内存
await processMessage(value);
}
}