WebSocket子协议通过Sec-WebSocket-Protocol头在握手阶段协商,未成功则ws.protocol为空字符串;客户端用构造函数第二参数传字符串或数组,服务端须原样返回匹配值,否则协商失败。

WebSocket子协议不是“设置”出来的,而是客户端和服务端在握手阶段通过 Sec-WebSocket-Protocol 头协商达成的一致;没协商成功,ws.protocol 就是空字符串——这意味着你后续所有按子协议写的解析逻辑都会 silently fail。
JavaScript 客户端怎么传子协议名
用 WebSocket 构造函数第二个参数,支持字符串或字符串数组:
- 单个协议:
new WebSocket("wss://api.example.com", "json-rpc") - 多个候选(按优先级排序):
new WebSocket("wss://api.example.com", ["myapp-v2", "myapp-v1"]) - 传空数组或
null会直接忽略该头,服务端收不到Sec-WebSocket-Protocol - 协议名必须是合法 token:只含小写字母、数字、
.、-,不能有空格或下划线
服务端必须显式返回匹配的 Sec-WebSocket-Protocol 响应头
浏览器不校验语义,只做严格字符串匹配。服务端哪怕只多一个空格、大小写不一致、或返回了不在客户端列表里的值,ws.protocol 都为空。
WebSocket 8.18.2 是该协议规范的一个重要迭代版本,主要优化了连接稳定性与数据传输效率。它通过全双工通信机制,允许客户端与服务器在单一长连接上实时交换数据,大幅降低传统 HTTP 轮询的开销。该版本增强了心跳保活、自动重连及二进制帧传输能力,适用于即时通讯、在线游戏及金融行情推送等低延迟场景,为开发者提供更可靠的实时网络交互基础。
- Node.js +
ws库:用handleProtocols选项返回字符串,如return "json-rpc";返回null表示拒绝所有 - FastAPI:
await websocket.accept(subprotocol="json-rpc")—— 这个字符串必须出现在客户端传入的列表中 - .NET
ClientWebSocket:调用options.AddSubProtocol("json-rpc"),不是设属性 - 代理(如 Nginx、CDN)可能默认过滤
Sec-WebSocket-Protocol头,需显式放行:proxy_pass_request_headers on;+ 检查proxy_set_header是否覆盖
如何确认子协议是否协商成功
别靠猜,直接看 Network 面板里的 WebSocket 握手请求/响应头,或连接打开后立刻检查 ws.protocol:
ws.onopen = () => {
console.log("Negotiated:", ws.protocol); // 输出 "json-rpc" 或 ""
};
- 如果输出空字符串,先检查请求头是否有
Sec-WebSocket-Protocol,再查响应头是否原样返回且值匹配 -
ws.protocol是只读属性,无法运行时修改;协商失败后不能“补救”,只能重连 - 某些可靠协议(如
json.reliable.webpubsub.azure.v1)要求客户端严格遵循帧格式和重连逻辑,ws.protocol不匹配会导致服务端主动关闭连接(关闭码 1007)
最常被忽略的一点:子协议协商发生在 TCP 连接建立后的 HTTP 升级阶段,它不提供任何运行时保障——一旦协商完成,后续所有消息都必须符合该协议定义的结构,浏览器不会帮你校验字段、类型或顺序。所谓“协议”,是契约,不是建议。

















