WebSocket通过子协议机制实现多版本兼容,客户端传协议数组、服务端精确匹配并绑定专属处理器,协商失败会导致语义断连。

WebSocket 本身不内置版本号字段用于业务逻辑区分,但通过 子协议(Subprotocol)机制 可以安全、标准地实现客户端与服务端的多版本兼容。关键不是改 WebSocket 协议版本(如 Sec-WebSocket-Version: 13 是固定且不可变的),而是用子协议名表达“应用层语义版本”,让新旧客户端共用同一端口、同一路径,由服务端按规则路由到对应处理逻辑。
用语义化子协议名标识版本契约
子协议名不是随便起的标签,它代表一套明确的通信契约:字段结构、错误码、心跳行为、序列化格式等。命名必须体现兼容边界:
- "v2.chat.example.com" —— 表示第二版聊天协议,服务端必须严格按 v2 规则解析和响应
- "api-1.5+json" —— 表明支持 1.5 及以上版本,且只接受 JSON 格式载荷,可启用新增字段但不做破坏性变更
- "legacy-v1" —— 专为老客户端兜底,禁用所有 v2+ 特性,不返回新字段,跳过新校验
避免使用 "chat"、"myapp" 等无区分度名称;禁止在协议名中嵌入动态值(如时间戳、用户 ID),否则无法匹配。
前端按降级顺序传协议数组
创建 WebSocket 实例时,第二个参数必须是字符串或字符串数组,且顺序决定协商优先级:
立即学习“Java免费学习笔记(深入)”;
- 新版页面:
new WebSocket("wss://api.example.com", ["v3.api.example.com", "v2.api.example.com", "legacy-v1"]) - 旧版 App:
new WebSocket("wss://api.example.com", ["v2.api.example.com"])或仅["legacy-v1"]
服务端从左到右遍历,取第一个自己支持的协议确认。若传空数组、null、带空格字符串(如 ["chat v2"])或遗漏第二个参数,请求头将不含 Sec-WebSocket-Protocol,导致协商失败——ws.protocol 为空,后续所有按协议设计的解析都会静默出错。
服务端精确匹配并绑定独立处理器
子协议协商成功后,ws.protocol 才有值;否则为空,所有依赖协议的逻辑(如消息解包、字段校验)将失效:
- FastAPI 中需显式调用
await websocket.accept(subprotocol=matched_protocol),且传入值必须与客户端数组中某一项完全一致(大小写敏感) - Node.js(ws 库)需在
upgrade事件中解析req.headers['sec-websocket-protocol'],选取首个支持项,写入响应头Sec-WebSocket-Protocol - 每个协议名应绑定专属处理器:v2 处理器不解析 v3 新字段,legacy-v1 处理器绕过所有 v2+ 的中间件校验
协议一旦确认,整个连接生命周期内不可更改;版本升级必须重连。
避开静默失败陷阱
子协议协商失败不会抛网络错误,但会导致“语义断连”:消息能发出去,对方却用错协议解析——例如 v3 客户端发了新字段,服务端因未协商成功而按 legacy-v1 解析,直接忽略或报错。
- 务必检查浏览器控制台 Network → WS → Headers,确认请求头含
Sec-WebSocket-Protocol,响应头含同值 - 服务端日志中打印
ws.protocol,为空即表示协商未生效 - 前端连接建立后,立即检查
socket.protocol是否符合预期,不符则主动关闭重试


















