跳到主要内容

WebSocket 速查表

本文提供 WebSocket 开发的快速参考,包括常用 API、代码片段和配置选项。建议收藏本页,在开发过程中快速查阅。

协议规范

项目说明
协议标准RFC 6455
WebSocket over HTTP/2RFC 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`);

连接状态

状态说明
CONNECTING0连接中,握手进行中
OPEN1已连接,可以通信
CLOSING2关闭中,正在执行关闭握手
CLOSED3已关闭,无法通信
// 状态检查
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]));

实例属性

属性类型说明
urlstring只读,连接的 URL
readyStatenumber只读,连接状态
protocolstring只读,服务器选定的子协议
extensionsstring只读,服务器选定的扩展
bufferedAmountnumber只读,已排队未发送的字节数
binaryTypestring二进制数据格式,'blob''arraybuffer'

关闭连接

socket.close();
socket.close(1000, '正常关闭');
socket.close(3000, '自定义原因');

关闭码(完整列表)

标准关闭码(RFC 6455 定义)

代码名称说明是否可发送
1000Normal Closure正常关闭,连接目的已达成
1001Going Away端点离开(如页面关闭、服务器关闭)
1002Protocol Error协议错误,收到不符合规范的数据
1003Unsupported Data不支持的数据类型(如收到二进制但只支持文本)
1004Reserved保留,未来可能定义-
1005No Status Rcvd未收到状态码(保留,不应发送)
1006Abnormal Closure异常关闭,未收到关闭帧(保留,不应发送)
1007Invalid Frame Payload无效的负载数据,消息格式错误
1008Policy Violation策略违规,通用错误码
1009Message Too Big消息过大,超出服务器处理能力
1010Mandatory Ext缺少必要的扩展
1011Internal Error服务器内部错误,无法完成请求
1012Service Restart服务重启,稍后可重连
1013Try Again Later服务器繁忙,稍后重试
1014Bad Gateway网关或代理服务器错误
1015TLS HandshakeTLS 握手失败(保留,不应发送)

扩展关闭码(IANA 注册)

代码范围用途说明
3000-3999库/框架使用由 WebSocket 库或框架定义
4000-4999应用自定义由应用程序自行定义

常用自定义关闭码示例

代码名称说明
4000User Logout用户主动登出
4001Session Expired会话过期
4002Reconnect Required需要重新连接
4003Rate 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

  1. 打开开发者工具 (F12)
  2. 切换到 Network 标签
  3. 筛选 WS (WebSocket)
  4. 点击连接查看消息

日志包装

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关闭帧
0x9Ping 帧
0xAPong 帧

握手头部

客户端请求

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

浏览器兼容性

浏览器最低版本备注
Chrome4+完全支持
Firefox4+完全支持
Safari5+完全支持
Edge12+完全支持
Opera11+完全支持
IE10+部分支持

特性检测

if ('WebSocket' in window) {
// 支持 WebSocket
} else {
// 降级到轮询
}

相关链接

资源链接
RFC 6455https://tools.ietf.org/html/rfc6455
MDN WebSocket APIhttps://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/websockethttps://github.com/gorilla/websocket
Python websocketshttps://websockets.readthedocs.io/

WebSocketStream API 速查

浏览器支持

浏览器支持版本
Chrome124+
Edge124+
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 对比

特性WebSocketWebSocketStream
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);
}
}