WebSocket
是什么
WebSocket 是一种在单个 TCP 连接上实现全双工持久化通信的协议。是一套协议标准。2011 年被 IETF 标准化为 RFC 6455。
解决什么问题
解决过去客户端和服务端全双工通信没有专用协议的问题。
过去客户端和服务端需要双向通信的场景下,不得不滥用 HTTP 连接 —— 一边轮询服务器获得更新,一边以独立的 HTTP 调用发送上行通知。这带来了一系列问题:
- 服务端被迫为每个客户端使用多个不同的 TCP 连接,一个用于向客户端发送消息,每一条入站消息,还要再开一条新连接。
- 线缆协议开销很高,每条客户端到服务器的消息都要携带一个 HTTP 头。
- 客户端脚本被迫维护一个从出站连接到入站连接的映射,以追踪应答。
WebSocket 的解决思路是使用单一 TCP 链接来承载双向流量。结合 WebSocket API,为网页和远程服务器的双向通信提供 HTTP 轮询之外的一种替代方案。
协议概述
Websocket 协议由两部分组成:一次握手和后续的数据传输。
🤝握手
来自客户端的握手如下所示:
GET /chat HTTP/1.1
Host: server.example.com
Origin: http://example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
来自服务端的握手如下所示:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
- Sec-WebSocket-Protocol:子协议选择器,用于表明客户端可接受哪些子协议(叠加在 websocket 协议之上的应用层协议);服务器在这些可接受的协议中选择一个(或一个都不选),并在握手中回显该值以表明已选择该协议。
- Sec-WebSocket-Key:从客户端发送到服务器,提供服务器用于证明其收到了合法 WebSocket 握手的一部分信息。用来确保服务器不会接受来自非 websocket 客户端(如 HTTP 客户端)的连接。
- Sec-WebSocket-Accept:从服务器发送到客户端,已确认服务器愿意发起 WebSocket 连接。计算方式:将 Sec-WebSocket-Key 和 全局唯一标识符拼接,然后对其计算 SHA-1 哈希,再进行 base64 编码。Websocket 客户端收到这个之后会对其进行检查,判断是否与期望值匹配。
- **Upgrade:**声明客户端想要把当前连接切换(升级)到的协议
- **Connection:**声明本次连接需要由逐跳(hop-by-hop)处理的控制选项,告诉两端哪些头字段 / 行为只在当前一跳有效、不能向下游转发
📞 数据传输
一旦客户端和服务器都发送了各自的握手且握手成功,数据传输部分即开始。
握手成功之后,客户端和服务器以消息的概念单元来回传输数据。在线缆上,一条消息由一个或多个帧组成。每个帧都有一种关联的类型。属于同一条消息的每个帧包含相同类型的数据。类型可以概括为以下三类:
- 文本数据
- 二进制数据
- 控制帧
关闭握手
服务端或客户端任何一方可以通过发送一个控制帧来开始关闭握手。
A 主动发出关闭连接的 Close 帧之后,A 不再继续发送消息。
B 收到 A 发出的 Close 帧之后,也发出一个 Close 帧作为响应,并立即停止发送消息。
WebSocket URI
Websocket 协议定义了两种 URI 方案。
ws-URI = "ws:" "//" host [ ":" port ] path [ "?" query ]
wss-URI = "wss:" "//" host [ ":" port ] path [ "?" query ]
ws://:明文 WebSocket, 对应 HTTP 的 http://,默认端口 80
wss://:加密 WebSocket,走 TLS,对应 HTTP 的https://,默认端口 443
Heartbeat 心跳机制
为什么需要 Heartbeat?
TCP 连接可能因为网络问题半开(一方不知道对端已失效),防火墙代理可能回收空闲连接。Heartbeat 能够在空闲时维持连接,并尽早发现断线的情况。
Websocket 协议提供了心跳的机制(Ping/Pong 控制帧),但并没有做强制。
不过常见的 websocket 库(eg:浏览器原生 WebSocket、Socket.IO、ws)都有各自的 heartbeat 和重连封装。
适用场景
适合需要服务端和客户端全双工通信的场景。例如:
- AI 应用中需要客户端和服务端频繁交互的实时通道
- 在线聊天
- 多人协同编辑
- 在线游戏
- 物联网设备上报与指令下发
局限
- 传输层基于 TCP ,存在队头阻塞,在高吞吐多路复用的实时场景下不如 QUIC / WebRTC
- 协议能力上没有多路复用,没有压缩,没有路由
示例:一次完整的 ws 通信
一次完整的 WebSocket 协议通信,从建连到结束连接,过程中发生了哪些事情?
1️⃣ Client 发起「握手」
WebSocket 客户端的握手就是一个 HTTP Upgrade 请求
2️⃣ Server 收到 ws 客户端的握手请求,返回 101 状态码,
3️⃣ 握手成功,开始数据传输
此时没有传输正式数据,浏览器原生的 WebSocket 的 Heartbeat 机制在工作,向服务器发送 ping 控制帧,服务器收到之后返回了 pong 控制帧。
4️⃣ 客户端向服务器发送数据帧,服务器收到数据帧之后响应请求,然后返回结果数据帧
(此处的_type_和_text__ 均为客户端和服务器自定义的字段,与库无关)_
5️⃣ 客户端主动结束连接
Close 帧在「开发者工具-Network」面板里并不可见。
WebSocket vs SSE
| 维度 | SSE | WebSocket |
|---|---|---|
| 方向 | 单向(服务端→客户端) | 全双工 |
| 自动重连 | 内置 + Last-Event-ID 续传 | 无,需自建 |
| 数据格式 | 纯文本(可自定义事件类型) | 文本 + 二进制 |
| 握手复杂度 | 普通 HTTP 流 | Upgrade + 掩码 + 帧 |
| HTTP/2 多路复用 | 天然支持 | 需单独协商 / 独立连接 |
| 代理 / CDN 友好 | 很好 | 依赖设备支持 |
| 服务端资源 | 一条下行流 | 全双工套接字 |
附录
原文链接
演示项目
参考资料
名词解释
- RFC,Request for Comments(中文常译作“请求评议”或“征求意见稿”),是由 IETF 等组织发布的一系列编号技术文档,用来描述互联网协议、方法、行为、研究和实践;可以看作是互联网技术的“正式技术说明书 + 标准档案”。
- **IETF,**Internet Engineering Task Force,互联网工程任务组,是成立于 1986 年的全球最重要互联网技术标准组织,负责制定和发布互联网核心协议与 RFC 文档。