websocket网络通信协议
websocket网络通信协议
参考:websocket_rfc6455
网络:
- 网络编程:解决1对多通信问题(1个server对N个client)
- epoll
- reactor
- 网络协议:解决一个客户端与一个服务器的通信问题
- http
- websocket
websocket
基于tcp的协议,解决粘包和拆包问题.
websocket头
WebSocket 的头信息结构需区分握手阶段(基于 HTTP 协议)和数据传输阶段(WebSocket 帧格式),两者的结构差异较大:
一、握手阶段:基于 HTTP 头信息
WebSocket 握手是通过 HTTP 协议完成的(客户端发送 HTTP 请求,服务器返回 HTTP 响应),因此头信息遵循 HTTP 格式,核心字段如下:
1. 客户端握手请求头(HTTP GET 请求)
GET /chat HTTP/1.1
Host: example.com:8080
Upgrade: websocket // 核心:请求升级到 WebSocket 协议
Connection: Upgrade // 配合 Upgrade,表示连接要升级
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 随机字符串,用于服务器验证
Sec-WebSocket-Version: 13 // 客户端支持的 WebSocket 版本(当前标准为 13)
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits // 可选:协商扩展(如压缩)
Sec-WebSocket-Protocol: chat, superchat // 可选:协商子协议(应用层自定义协议)
Origin: http://example.com // 可选:客户端来源(用于服务器跨域验证)
关键字段说明:
Upgrade: websocket
+Connection: Upgrade
:强制触发 HTTP 协议升级机制,声明要从 HTTP 切换到 WebSocket。Sec-WebSocket-Key
:16 字节随机值的 Base64 编码,用于服务器生成Sec-WebSocket-Accept
进行验证(防止误判非 WebSocket 响应)。Sec-WebSocket-Version: 13
:指定 WebSocket 版本(13 是当前标准,早期版本如 8、10 已淘汰)。
2. 服务器握手响应头(101 状态码)
HTTP/1.1 101 Switching Protocols // 核心:确认协议升级
Upgrade: websocket // 呼应客户端,确认升级到 WebSocket
Connection: Upgrade // 确认连接保持升级状态
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 服务器验证后的密钥(基于客户端 Key + GUID 计算)
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits // 可选:确认支持的扩展
Sec-WebSocket-Protocol: chat // 可选:确认使用的子协议(从客户端请求中选择)
关键字段说明:
101 Switching Protocols
:HTTP 状态码,表示服务器同意协议升级。Sec-WebSocket-Accept
:服务器通过“客户端 Key + 固定 GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11
)”拼接后,经 SHA-1 哈希 + Base64 编码生成,客户端会验证此值以确认服务器支持 WebSocket。
二、数据传输阶段:WebSocket 帧(Frame)头结构
握手完成后,数据传输使用二进制帧格式,每个帧的头信息是二进制结构(而非文本 HTTP 头),格式如下(单位:bit):
字段 | 位数 | 含义 |
---|---|---|
FIN | 1 | 是否为“最后一帧”:1 = 该帧是消息的最后一部分;0 = 后续还有帧(用于分片消息)。 |
RSV1-3 | 1-1-1 | 保留位,用于扩展协议(如压缩),未使用时必须为 0,否则接收方会断开连接。 |
Opcode | 4 | 帧类型: - 0x0:延续帧(分片消息的中间帧) - 0x1:文本帧(数据为 UTF-8 编码) - 0x2:二进制帧(任意二进制数据) - 0x8:关闭连接帧 - 0x9:Ping 帧(心跳检测) - 0xA:Pong 帧(Ping 响应) |
Mask | 1 | 是否使用掩码:1 = 帧数据被掩码加密(客户端发送的帧必须设为 1);0 = 未加密(服务器发送的帧必须设为 0)。 |
Payload Length | 7 / 7+16 / 7+64 | 有效载荷(数据)长度: - 若值 ≤ 125:直接表示长度(单位:字节)。 - 若值 = 126:后续 2 字节(16 位无符号整数)表示长度。 - 若值 = 127:后续 8 字节(64 位无符号整数)表示长度。 |
Masking Key | 0 / 32 | 掩码密钥:仅当 Mask=1 时存在(4 字节),用于解密 Payload Data(客户端发送的数据必须用此密钥加密)。 |
Payload Data | 可变 | 实际传输的数据(文本/二进制/控制信息),长度由 Payload Length 指定。 |
帧头示例(简化):
假设客户端发送一个 10 字节的文本帧(完整消息,非分片):
FIN=1
(最后一帧)RSV1-3=000
(无扩展)Opcode=0x1
(文本帧)Mask=1
(客户端发送,需掩码)Payload Length=10
(≤125,直接表示)Masking Key=0x12345678
(4 字节随机密钥)Payload Data=10字节文本数据
(经掩码加密)
总结
WebSocket 的“头信息”分两个阶段:
- 握手阶段:基于 HTTP 文本头,核心是通过
Upgrade
等字段完成协议升级和验证(确保双方支持 WebSocket)。 - 数据阶段:使用二进制帧头,通过
FIN
、Opcode
、Payload Length
等字段定义数据传输规则(如消息分片、数据类型、长度等),并通过掩码保证客户端发送数据的安全性。
这种设计既兼容了 HTTP 基础设施(握手阶段),又实现了高效的全双工数据传输(帧结构阶段)。
websocket面试题
为什么websocket要握手?
WebSocket 握手(Handshake)是从 HTTP 协议“升级”到 WebSocket 协议的关键过程,其核心目的是在 HTTP 基础设施(如代理、防火墙、服务器)的兼容前提下,建立一个持久化的全双工通信通道。你提到的“版本升级”“兼容 HTTP 协议”“验证双方身份”是核心原因,具体细节如下:
1. 本质是“协议版本升级”:从 HTTP 到 WebSocket
HTTP 是请求-响应模式的短连接协议(每次通信需客户端主动发起请求),而 WebSocket 是全双工的长连接协议(客户端和服务器可随时双向发送数据)。两者的通信模式完全不同,因此需要通过“握手”完成协议切换:
- 客户端发送HTTP 升级请求,明确告知服务器:“我想从 HTTP 切换到 WebSocket 协议”,并携带 WebSocket 相关参数(如版本号、支持的扩展等)。
- 服务器若支持 WebSocket,会返回101 Switching Protocols响应,确认“同意升级”,从此连接不再遵循 HTTP 规则,而是使用 WebSocket 帧格式传输数据。
这一步是“版本升级”的核心:通过 HTTP 协议的“Upgrade”机制,完成从短连接到长连接、从单向请求到双向通信的转换。
2. 兼容 HTTP 协议:穿透现有网络基础设施
WebSocket 设计时必须兼容现有基于 HTTP 的网络环境(如代理服务器、负载均衡器、防火墙等),否则会被这些中间设备拦截或误处理。握手的关键作用就是“伪装”成 HTTP 流量,通过基础设施的验证:
- 握手过程完全使用 HTTP 报文格式(如
GET
请求方法、Host
头、Upgrade
头),让中间设备误认为这是一个普通 HTTP 请求,从而允许其通过。 - 只有完成握手后,通信才切换到 WebSocket 帧格式,但此时连接已建立,中间设备通常不会再拦截长连接的后续数据。
若没有握手过程,直接使用全新的协议格式,会被大多数 HTTP 代理/防火墙阻断(它们只允许 HTTP 标准流量),导致 WebSocket 无法在现有网络中普及。
3. 验证双方“协议能力”:确保通信参数一致
握手过程包含简单的“验证机制”,核心是确认双方都理解并支持 WebSocket 协议,避免误连接(如客户端连接到不支持 WebSocket 的 HTTP 服务器):
- 客户端发送
Sec-WebSocket-Key
(一个随机字符串),服务器需通过特定算法(SHA-1 哈希 + Base64 编码)计算出Sec-WebSocket-Accept
并返回。 - 客户端验证服务器返回的
Sec-WebSocket-Accept
是否正确,若正确则确认“服务器确实支持 WebSocket”,否则断开连接。
这个验证不是“身份认证”(不涉及用户身份),而是“协议能力验证”,确保双方在同一套规则下通信。此外,握手时还会协商:
- WebSocket 版本(
Sec-WebSocket-Version
),避免版本不兼容; - 支持的扩展(
Sec-WebSocket-Extensions
)和子协议(Sec-WebSocket-Protocol
),确定数据压缩、自定义协议等细节。
总结
WebSocket 握手是一个“一举三得”的设计:
- 通过协议升级,从 HTTP 短连接转换为 WebSocket 长连接;
- 借助HTTP 兼容,穿透现有网络基础设施(代理、防火墙等);
- 通过协议验证,确保双方支持相同的 WebSocket 规则,避免无效连接。
这使得 WebSocket 既能享受长连接全双工的优势,又能在现有 HTTP 生态中无缝部署,成为实时通信(如聊天、弹幕、实时数据更新)的主流方案。
websocket握手为什么要GUID?
避免数据太短,无法hash.
WebSocket 握手过程中引入固定 GUID(258EAFA5-E914-47DA-95CA-C5AB0DC85B11
)的核心目的,并非单纯“避免数据太短无法哈希”,而是通过标准化的固定字符串,确保握手验证的唯一性和安全性,防止“误判”或“跨协议攻击”。
具体背景:握手验证的流程
WebSocket 握手的验证逻辑是:
- 客户端生成一个 16 字节的随机值,用 Base64 编码后作为
Sec-WebSocket-Key
发送给服务器。 - 服务器将这个
Sec-WebSocket-Key
与固定 GUID 拼接,得到一个新字符串。 - 对新字符串做 SHA-1 哈希,再将哈希结果用 Base64 编码,得到
Sec-WebSocket-Accept
,返回给客户端。 - 客户端重复服务器的计算过程,验证返回的
Sec-WebSocket-Accept
是否一致,一致则确认握手成功。
GUID 的核心作用:防止“误匹配”
如果没有这个固定 GUID,验证逻辑会存在严重漏洞:
- 假设客户端发送的
Sec-WebSocket-Key
是一个随机字符串,服务器只需对其进行某种哈希处理后返回。 - 但此时可能出现**“巧合匹配”**:比如客户端误将一个普通 HTTP 服务器的响应(非 WebSocket 握手)当作 valid 验证结果(例如,服务器返回的某个字符串恰好与客户端的哈希计算结果一致)。
而固定 GUID 的引入,相当于给验证过程加了一个**“唯一标识符”**:
- 只有知道这个 GUID 的服务器(即真正支持 WebSocket 协议的服务器),才能生成正确的
Sec-WebSocket-Accept
。 - 普通 HTTP 服务器或其他协议的服务器不知道这个 GUID,无法生成匹配的响应,客户端就能明确区分“真正的 WebSocket 服务器”和“其他服务器”。
关于“数据长度”的补充
客户端发送的 Sec-WebSocket-Key
是 16 字节随机值(Base64 编码后为 24 字符),这个长度本身足够进行 SHA-1 哈希(SHA-1 可处理任意长度数据,哪怕只有 1 字节)。
因此,“避免数据太短”是次要因素,真正核心是通过固定 GUID 确保验证逻辑的“协议唯一性”——只有遵循 WebSocket 标准的服务器才能通过验证,从根本上杜绝跨协议的误判风险。
总结
WebSocket 握手引入固定 GUID,是为了给验证过程增加一个**“协议专属密钥”**:
- 确保服务器确实理解 WebSocket 协议(知道这个 GUID),而非其他协议的服务器。
- 彻底避免因随机巧合导致的“误验证”,保证握手的安全性和准确性。
这个设计让 WebSocket 在兼容 HTTP 基础设施的同时,又能可靠地区分协议类型,是协议设计中“安全性与兼容性平衡”的典型案例。