- 什么是 WebSocket?
WebSocket 是一种基于 TCP 的网络通信协议,定义于 RFC 6455。它允许客户端与服务器建立持久连接,并在同一条连接上进行双向、全双工通信。
传统的 HTTP 通信通常由客户端发起请求,服务器返回响应。如果客户端希望实时获取服务器端的新数据,往往需要重复发送请求,或者采用长轮询等技术。
WebSocket 则不同。连接建立后,客户端和服务器都可以主动发送数据,不必每次通信都重新发起 HTTP 请求。
例如,在即时聊天应用中:
-
用户 A 发送一条消息,服务器可以立即将消息推送给用户 B。
-
服务器可以主动通知客户端连接状态变化。
-
客户端可以在同一条连接上持续发送实时数据。
WebSocket 常见的应用场景包括即时通信、在线游戏、实时监控、交易行情、协同编辑,以及基于 Web 的实时语音和视频信令。
2. WebSocket 与 HTTP 有什么区别?
WebSocket 并不是 HTTP 的替代品。两者解决的问题有所不同。
| 对比项 | HTTP | WebSocket |
|---|---|---|
| 通信模式 | 通常采用请求-响应模式 | 全双工、双向通信 |
| 连接 | HTTP/1.1 可复用连接;HTTP/2 也支持多路复用 | 建立后保持持久连接 |
| 数据发送 | 通常由客户端请求触发 | 双方都可以主动发送 |
| 通信开销 | 每次请求有相应的协议开销 | 建连后使用较轻量的数据帧 |
| 典型场景 | REST API、网页资源、普通业务请求 | 实时消息、事件推送、双向流式交互 |
需要注意,HTTP/1.1 本身也支持持久连接,因此不能简单地认为 HTTP 每次请求都需要重新建立 TCP 连接。WebSocket 的核心优势在于建立连接后,通信不再局限于传统的 HTTP 请求-响应模式。
另外,WebSocket 不会自动提供业务消息的持久化、可靠重放、用户身份授权或消息顺序保证。此类能力通常需要由应用层设计。
3. WebSocket 如何建立连接?
WebSocket 通常通过 HTTP Upgrade 机制发起握手。客户端先发送一个特殊的 HTTP/1.1 请求,要求服务器将连接协议升级为 WebSocket。
例如,客户端可能发送如下请求:
GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: https://example.com
其中几个关键字段的含义如下:
-
Upgrade: websocket:请求切换到 WebSocket 协议。 -
Connection: Upgrade:表明本次请求希望升级连接。 -
Sec-WebSocket-Key:客户端生成的随机 Base64 编码值,用于协议握手验证。 -
Sec-WebSocket-Version: 13:指定使用的 WebSocket 协议版本。 -
Origin:浏览器客户端可携带该字段,服务器可据此执行来源校验。
如果服务器接受升级请求,会返回类似下面的响应:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Accept 并不是任意生成的字符串。服务器将客户端提供的 Sec-WebSocket-Key 与固定的 GUID 拼接,计算 SHA-1,再进行 Base64 编码,得到该字段的值。
计算过程为:
Sec-WebSocket-Accept =
Base64(
SHA1(
Sec-WebSocket-Key +
"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
)
)
这个过程主要用于确认双方使用了正确的 WebSocket 握手机制,并不是身份认证,也不能代替 TLS 加密。
当客户端收到有效的 101 Switching Protocols 响应并完成握手校验后,双方即可在连接上按照 WebSocket 协议交换数据。
握手完成后发生了什么?
在经典的 HTTP/1.1 WebSocket 连接中,握手成功后,连接不再继续承载普通 HTTP 请求和响应,而是交换 WebSocket 数据帧。
需要特别注意:WebSocket over HTTP/2 使用不同的建立方式,通过 HTTP/2 的扩展 CONNECT 机制建立隧道,而不是在 HTTP/2 连接上直接使用 HTTP/1.1 的 101 Switching Protocols 握手。
4. WebSocket 数据帧
WebSocket 的数据并不是简单地将文本或二进制数据直接写入 TCP 连接,而是封装成 WebSocket 帧。
一个基本的数据帧包含以下字段:
-
FIN:表示当前帧是否为一条消息的最后一帧。 -
Opcode:标识帧的类型,例如文本、二进制数据或控制帧。 -
MASK:表示负载数据是否经过掩码处理。 -
Payload Length:负载长度。 -
Masking-Key:当帧被掩码时使用的密钥。 -
Payload Data:实际的数据内容。
常见的 Opcode 包括:
| Opcode | 含义 |
|---|---|
0x0 |
延续帧 |
0x1 |
文本帧 |
0x2 |
二进制帧 |
0x8 |
关闭连接 |
0x9 |
Ping |
0xA |
Pong |
文本消息使用 UTF-8 编码。二进制消息则适用于音频片段、图片、压缩数据等内容。
一条 WebSocket 消息不一定对应一个帧。当消息较大时,可以拆分成多个帧发送,接收方根据帧标志和类型重新组装消息。
为什么客户端发送的数据需要 Mask?
按照 RFC 6455,客户端发送给服务器的 WebSocket 帧必须经过掩码处理,而服务器发送给客户端的帧不使用这种掩码。
掩码通过一个 32 位密钥与负载字节进行循环异或计算:
masked_payload[i] =
original_payload[i] XOR masking_key[i % 4]
这种机制的主要目的,是降低特定情况下中间网络设备将 WebSocket 流量误识别为其他协议的风险。
掩码不是加密。 它不能保护数据机密性,也不能代替 TLS。需要加密传输时,应使用 wss://。
5. WebSocket 的 Ping、Pong 与心跳机制
长连接面临一个实际问题:连接看起来仍然存在,但对端可能已经不可达。
例如:
-
网络发生短暂中断。
-
NAT 或防火墙清理了空闲连接。
-
服务器进程异常或连接状态失效。
-
移动设备进入休眠状态。
WebSocket 定义了 Ping 和 Pong 控制帧,可用于检测连接状态。
典型流程如下:
Client Server
| |
| -------- WebSocket Ping ----> |
| |
| <------- WebSocket Pong ----- |
| |
| Connection OK |
如果发送 Ping 后,在设定的超时时间内没有收到对应的 Pong,应用可以将连接判定为疑似失效,并执行关闭或重连策略。
需要区分两个概念:
-
协议级 Ping/Pong:WebSocket 定义的控制帧。
-
应用层心跳 :例如发送
{"type":"heartbeat"}这样的 JSON 消息。
两者并不相同。应用层心跳需要双方应用程序理解相应的消息格式;协议级 Ping/Pong 则由 WebSocket 协议栈处理。
实际部署时,还应结合负载均衡器、反向代理和防火墙的空闲超时配置设计心跳周期。心跳间隔过短会增加开销,过长则可能导致失效连接被发现得太晚。
6. WebSocket 连接如何关闭?
WebSocket 提供专门的 Close 控制帧,用于关闭连接。
典型的正常关闭流程如下:
Client Server
| |
| -------- Close Frame -------> |
| |
| <------- Close Frame -------- |
| |
| TCP Connection |
| Closed |
Close 帧可以携带关闭状态码和可选的原因描述。双方完成 WebSocket 关闭握手后,再关闭底层 TCP 连接。
一些常见状态码包括:
-
1000:正常关闭。 -
1001:端点离开,例如服务器关闭或客户端导航离开。 -
1002:协议错误。 -
1003:收到无法接受的数据类型。 -
1006:表示异常关闭时使用的本地报告状态,不能作为实际发送的 Close 帧状态码。
如果 TCP 连接直接断开,或者网络突然中断,双方可能无法完成正常的 WebSocket 关闭握手。因此,应用程序不能仅依赖 Close 帧判断连接是否健康,还需要处理底层连接错误和超时。
7. WebSocket 与 TCP 的关系
从协议分层来看,WebSocket 通常运行在 TCP 之上:
+----------------------------+
| Application |
| Chat / Signaling / API |
+----------------------------+
| WebSocket |
| Message / Frame / Control |
+----------------------------+
| TCP |
| Ordered Reliable Stream |
+----------------------------+
| IP |
+----------------------------+
TCP 提供可靠、有序的字节流传输;WebSocket 在其上定义消息边界、控制帧和双向通信语义。
这意味着 WebSocket 本身并不直接提供独立于 TCP 的消息传输通道。接收方必须根据 WebSocket 帧头解析消息边界,而不能将一次 TCP read() 返回的数据直接当成一条完整的 WebSocket 消息。
例如,发送方写入一条完整的 WebSocket 消息后,接收方可能:
-
一次读取就拿到完整帧;
-
多次读取才拿到完整帧;
-
一次读取中包含多个帧的全部或部分数据。
这是 TCP 字节流的正常行为,不代表 WebSocket 数据损坏。
对于需要实时传输的应用,TCP 还有一个重要特性:丢包时,后续数据可能因为重传而等待,即队头阻塞。WebSocket 不能消除这一特性,因为它通常运行在 TCP 之上。
8. WebSocket 的安全性
WebSocket 建立的是持久通信通道,因此安全设计不能只关注最初的握手请求。
使用 WSS
生产环境通常应使用 wss://,即运行在 TLS 保护下的 WebSocket 连接。
-
ws://:不提供传输层加密。 -
wss://:使用 TLS 保护传输数据。
WSS 可以保护传输过程中的数据机密性和完整性,但不能自动判断客户端是否有权访问某个业务资源。
进行身份认证和授权
握手成功并不代表客户端已经通过业务身份认证。服务端仍然需要验证用户身份,并针对每条连接及其操作执行必要的授权检查。
浏览器的 WebSocket API 对自定义握手请求头有限制,因此实际项目中常见的认证方式包括安全的 Cookie、受控的票据机制,或通过应用层消息完成认证。具体方案应结合浏览器、跨域要求和服务端架构选择。
校验 Origin
浏览器会在适用情况下发送 Origin 请求头。服务器可以根据允许的来源列表进行校验,以降低跨站 WebSocket 劫持(Cross-Site WebSocket Hijacking,CSWSH)风险。
但 Origin 并不是身份凭证,非浏览器客户端也可能自行构造该字段。
此外,服务端还应考虑消息大小限制、连接数限制、速率限制、输入校验,以及敏感数据日志脱敏等问题。
9. WebSocket 的工程实践
在实际系统中,建立 WebSocket 连接只是第一步。可靠的实时通信系统还需要处理连接生命周期和业务状态。
9.1 断线重连
网络连接可能随时断开,客户端通常需要实现重连机制。
建议采用指数退避,并增加随机抖动,避免大量客户端在服务恢复时同时重连。
例如:
retry_delay =
min(max_delay, base_delay * 2^retry_count)
实际实现时,可以在计算出的延迟上叠加随机抖动,并限制最大重试次数或设置整体重试策略。
9.2 消息丢失与重复
WebSocket 在一条正常工作的 TCP 连接内提供可靠、有序的字节传输,但这不意味着应用层业务消息一定不会丢失或重复。
例如,客户端发送了一条业务消息,服务器已经完成处理,但响应在连接断开前没有到达客户端。客户端重连后重发消息,就可能造成重复处理。
对于有业务价值的消息,可以考虑:
-
消息 ID;
-
幂等处理;
-
ACK 确认机制;
-
断线后的消息补拉;
-
服务端消息持久化。
是否需要这些机制,取决于业务对可靠性的要求。
9.3 服务端连接管理
对于高并发 WebSocket 服务,服务端通常需要维护连接与用户、会话或业务实体之间的映射关系。
需要特别关注:
-
每条连接的内存开销;
-
心跳检测和超时清理;
-
慢客户端造成的发送队列堆积;
-
负载均衡和连接迁移;
-
多实例之间的消息路由;
-
服务升级时的连接排空与重建。
如果连接数较大,应避免无界缓存待发送消息,并为发送队列设置容量上限和过载处理策略。
10. WebSocket 适合所有实时通信场景吗?
并不是。
WebSocket 适合需要在客户端和服务器之间进行双向实时交互的场景,但不同的通信需求可能需要不同的协议。
| 需求 | 可考虑的方案 |
|---|---|
| 普通业务请求 | HTTP / REST |
| 服务端向浏览器持续推送事件 | SSE |
| 浏览器与服务器双向实时通信 | WebSocket |
| 浏览器实时音视频媒体传输 | WebRTC |
| 消息代理与设备通信 | MQTT |
| 基于 HTTP 的远程过程调用 | gRPC |
这些技术并非完全互斥。例如,实时音视频应用可以使用 WebSocket 交换信令,而使用 WebRTC 传输媒体。
选择协议时,应综合考虑通信方向、消息规模、延迟要求、可靠性、网络环境和基础设施支持情况,而不是仅仅因为应用需要"实时"就直接选择 WebSocket。
总结
WebSocket 通过握手建立持久连接,并在 TCP 字节流之上定义数据帧、消息边界和控制机制,使客户端与服务器能够进行双向、全双工通信。
理解 WebSocket 时,最重要的是把握几个核心概念:
-
握手负责建立协议连接,不负责完成业务身份认证。
-
数据帧负责承载消息,但一次 TCP 读取不一定对应一条完整消息。
-
Ping/Pong 可以帮助检测连接状态,但不能保证业务消息不丢失。
-
TCP 提供可靠、有序的字节流,WebSocket 在其上提供消息和控制语义。
-
生产环境还需要设计安全认证、断线重连、消息幂等、连接管理和过载保护。
掌握这些机制,才能从理解协议本身进一步走向可靠的 WebSocket 服务设计与故障排查。