客户端通过websocket构造函数第二个参数传子协议名:字符串表示单协议,数组表示多个候选(按优先级排序),空数组/null/undefined会忽略sec-websocket-protocol头;协议名须为合法token(仅小写字母、数字、.、-)。

客户端怎么传子协议名
直接用 WebSocket 构造函数的第二个参数,别绕弯。传字符串就是单协议,传数组就是多个候选(按顺序优先级排列):
new WebSocket("wss://api.example.com", "json-rpc")new WebSocket("wss://api.example.com", ["myapp-v2", "myapp-v1"])
传空数组、null 或 undefined,浏览器会直接忽略 Sec-WebSocket-Protocol 请求头——服务端根本收不到协商信号。协议名必须是合法 token:只允许小写字母、数字、. 和 -,不能有空格、下划线或大写。
服务端怎么响应才有效
服务端不是“支持”就行,而是必须在 101 响应里原样返回客户端列表中的某一个值,一字不差。浏览器只做严格字符串匹配,多一个空格、大小写错一位、或者返回了不在客户端列表里的值,ws.protocol 就是空字符串。
- Node.js +
ws库:handleProtocols回调里 return 字符串,比如return "json-rpc";returnnull表示拒绝全部 - 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 覆盖。
怎么确认子协议协商成功了
别靠日志猜,也别等出问题再查。连接一打开就立刻读 ws.protocol:
ws.onopen = () => {
console.log("Negotiated:", ws.protocol); // 输出 "json-rpc" 或 ""
};
如果输出空字符串,立刻打开 Network 面板,看握手请求有没有 Sec-WebSocket-Protocol,再看响应头有没有同名且值完全一致的字段。只要其中一环断掉,后续所有按该子协议写的解析逻辑都会静默失败——比如你写了 JSON-RPC 的 request.id 校验,但实际连协议都没协商上,字段缺失也不会报错,只会卡住或崩溃。
子协议不是运行时开关,而是握手即锁定
ws.protocol 是只读属性,无法在连接建立后修改。协商失败只能重连,没有补救机制。更关键的是:子协议一旦协商成功,它不提供任何运行时保护。浏览器不会校验你发的消息是不是 JSON、字段对不对、opcode 是不是 0x1——它只管把字节流送过去。服务端若发现帧格式不符合子协议定义(比如该是 UTF-8 文本却收到二进制),可能直接关连接,返回关闭码 1007(Invalid frame payload data)。这意味着协议语义对齐必须在握手阶段完成,而不是靠运行时提示来兜底。











