WebSocket子协议是RFC 6455定义的握手协商机制,客户端须通过构造函数第二个参数传字符串或数组,服务端必须原样返回客户端数组中某一项,ws.protocol为空则协商失败,且协议一旦确立不可更改。

WebSocket 子协议(Subprotocol)不是可选插件,而是 RFC 6455 标准定义的握手协商机制,用于在建立连接时明确双方约定的应用层语义。它不改变传输层行为,但决定了后续所有消息的格式、字段含义和处理逻辑。
客户端怎么正确传子协议
必须通过 WebSocket 构造函数第二个参数传入,且只接受字符串或字符串数组:
- 单协议:直接传字符串,如
new WebSocket("wss://api.example.com", "json-rpc") - 多候选(推荐):按兼容优先级从高到低传数组,如
["v3.chat.example.com", "v2.chat.example.com", "legacy-v1"] - 协议名只能含小写字母、数字、点(.)和短横线(-),不能有空格、下划线或大写
- 传
null、undefined或空数组,浏览器会完全忽略Sec-WebSocket-Protocol请求头
服务端怎么响应才算有效
服务端不能“支持即可”,而必须在 101 响应中**原样返回**客户端数组中某一项,大小写、空格、顺序都必须一致:
- Node.js(
ws库):在handleProtocols回调中返回匹配的字符串,如return "v2.chat.example.com";返回null表示全部拒绝 - FastAPI:调用
await websocket.accept(subprotocol="v2.chat.example.com"),该值必须出现在客户端传入的数组里 - Nginx / CDN 等代理常默认过滤
Sec-WebSocket-Protocol头,需显式配置proxy_pass_request_headers on;并检查是否被覆盖
怎么确认子协议协商成功
别等出问题再查,连接一打开就立刻读取 ws.protocol:
立即学习“Java免费学习笔记(深入)”;
-
ws.onopen = () => console.log("Negotiated:", ws.protocol)—— 输出为空字符串说明协商失败 - 此时打开 Network 面板,检查请求头是否有
Sec-WebSocket-Protocol,响应头是否有同名且值完全一致的字段 - 只要任一环节不匹配(比如服务端返回了
v2.chat.example.com多一个空格),ws.protocol就是空,后续所有按该协议写的解析都会静默失效
子协议不是运行时开关,而是握手即锁定
ws.protocol 是只读属性,连接建立后无法更改。它不提供任何运行时校验:
- 浏览器不会检查你发的消息是不是 JSON、字段是否存在、opcode 是否合法
- 它只负责把字节流原样送达,语义解析全靠你自己的处理器——所以每个协议名必须绑定独立的处理逻辑
- v3 处理器不该尝试解析 v2 字段,legacy-v1 处理器必须跳过所有 v2+ 的新增校验


















