WebSocket 协议

13 阅读8分钟

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

维度SSEWebSocket
方向单向(服务端→客户端)全双工
自动重连内置 + Last-Event-ID 续传无,需自建
数据格式纯文本(可自定义事件类型)文本 + 二进制
握手复杂度普通 HTTP 流Upgrade + 掩码 + 帧
HTTP/2 多路复用天然支持需单独协商 / 独立连接
代理 / CDN 友好很好依赖设备支持
服务端资源一条下行流全双工套接字

附录

原文链接

www.yuque.com/yuqueyonghu…

演示项目

github.com/Qiuzcc/sse-…

参考资料

  1. RFC 6455 | IETF Datatracker
  2. RFC 6455 中文翻译版

名词解释

  1. RFC,Request for Comments(中文常译作“请求评议”或“征求意见稿”),是由 IETF 等组织发布的一系列编号技术文档,用来描述互联网协议、方法、行为、研究和实践;可以看作是互联网技术的“正式技术说明书 + 标准档案”。
  2. **IETF,**Internet Engineering Task Force,互联网工程任务组,是成立于 1986 年的全球最重要互联网技术标准组织,负责制定和发布互联网核心协议与 RFC 文档。