WebSocket子协议通过构造函数第二个参数声明,支持字符串或字符串数组;服务端须在Sec-WebSocket-Protocol响应头中精确返回客户端列表中的某一项,否则ws.protocol为空,导致语义断连。

子协议不是可选功能,而是你决定要不要让连接“讲同一种话”的开关。不声明或协商失败时,ws.protocol 为空,后续所有按协议设计的解析逻辑都会静默失效——这不是报错,而是更难排查的语义断连。
WebSocket构造函数里怎么传子协议
必须用第二个参数,类型只能是 string 或 string[],不能是对象、数字或带空格的字符串。
常见错误现象:
- 传了
["chat v2"](含空格)→ 服务端拒绝匹配,ws.protocol为空 - 传了
["chat", "json-rpc"]但服务端只支持"rpc"→ 协商失败 - 漏掉第二个参数,或传了
null/undefined→ 请求头不带Sec-WebSocket-Protocol,服务端无从选择
正确写法示例:
const ws = new WebSocket("wss://api.example.com", "myapp-v1");
// 或多选(按优先级从左到右)
const ws = new WebSocket("wss://api.example.com", ["myapp-v2", "myapp-v1"]);
FastAPI后端怎么接受并确认子协议
FastAPI 的 WebSocket 实例不自动处理子协议协商,必须显式调用 accept(subprotocol=...),且值必须严格等于客户端列表中的某一项。
容易踩的坑:
- 写成
await websocket.accept(subprotocol="MyApp-V1")(大小写不一致)→ 浏览器不认,ws.protocol仍为空 - 没传
subprotocol参数给accept()→ 连接虽建立,但响应头无Sec-WebSocket-Protocol,协商视为失败 - 客户端传了数组,服务端却硬编码固定一个值,未校验是否在客户端列表中 → 可能被中间代理拦截或触发安全策略拒绝
推荐做法:
@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
# 获取客户端声明的子协议列表
client_protocols = websocket.headers.get("sec-websocket-protocol", "").split(", ")
# 选第一个服务端支持的(注意 trim 和大小写)
selected = next((p for p in client_protocols if p in ["myapp-v1", "myapp-v2"]), None)
if not selected:
await websocket.close(code=4000, reason="Unsupported subprotocol")
return
await websocket.accept(subprotocol=selected)
为什么ws.protocol始终为空
这不是前端代码问题,而是握手阶段的协议匹配失败。浏览器只做字符串精确比对,不忽略空格、不转大小写、不截断。
调试关键点:
- 打开浏览器 Network 面板,找到 WebSocket 握手请求 → 查看 Request Headers 中是否有
Sec-WebSocket-Protocol,值是否为你传的字符串 - 查看 Response Headers → 是否存在同名 header,且值与请求中某一项完全一致
- 检查反向代理(如 Nginx、Cloudflare)是否默认剥离
Sec-WebSocket-Protocol头 → 需显式配置proxy_pass_request_headers on;和透传规则 - 某些测试工具(如 wscat)默认不发该头,需加
-s参数:wscat -c "ws://localhost:8000/ws" -s "myapp-v1"
Azure Web PubSub 等托管服务的子协议特殊性
像 json.reliable.webpubsub.azure.v1 这类子协议不是“可选功能”,而是强制契约:一旦声明,就必须严格按其消息格式、重连流程、心跳机制实现,否则连接会被服务端主动关闭。
典型陷阱:
- 只设了子协议名,但没实现
reconnect流程里的resumptionToken传递 → 断线重连后收不到历史消息 - 发送普通 JSON 字符串,但子协议要求顶层字段为
{"type":"message","data":{...}}→ 服务端静默丢弃 - 用
ws.send()发二进制,但子协议仅定义 JSON 文本交互 → 协议层直接拒绝
建议:优先用官方 SDK(如 @azure/web-pubsub-client),它已封装全部子协议细节;手动实现前,务必精读对应子协议的 RFC-style 规范文档,而非只看示例代码。











