跳到主要内容

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);
}
}