type
status
date
slug
summary
tags
category
icon
password
Sub-item
Last edited time
Oct 28, 2023 10:39 AM
Parent item
领域
WebSocket协议是一个独立的基于TCP的协议。它与HTTP唯一的关系是它的握手是由HTTP服务器解释为一个Upgrade请求。
为什么需要websocket
初次接触 WebSocket 的人,都会问同样的问题:我们已经有了 HTTP 协议,为什么还需要另一个协议?它能带来什么好处?答案很简单,因为 HTTP 协议有一个缺陷:通信只能由客户端发起。HTTP 协议做不到服务器主动向客户端推送信息。
这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。我们只能使用"轮询":每隔一段时候,就发出一个询问,了解服务器有没有新的信息。最典型的场景就是聊天室。
轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。因此,工程师们一直在思考,有没有更好的方法。WebSocket 就是这样发明的。
websocket协议的通信
websocket协议的通信分为3个部分:
- 打开通道阶段握手
- 数据传输
- 关闭通道阶段握手
打开通道阶段握手
WebSocket协议首先借助于HTTP协议建立通道。然后在此基础上用真正的WebSocket协议进行通信。
具体请求数据的实例:
其中重要的几个字段如下:
Origin
表明请求来自于哪个站点;
Host
表明目标主机;
Sec-WebSocket-Version
表明协议版本,要求双端版本一致。当前 WebSocket 协议版本默认为13
- Connection和Upgrade字段告诉服务器,客户端发起的是WebSocket协议请求。
- Sec-WebSocket-Extensions表示客户端想要表达的协议级的扩展。
- Sec-WebSocket-Key是一个Base64编码值,由浏览器随机生成,用于防止攻击者恶意欺骗服务端。
Sec-WebSocket-Accept
则是经过服务器确认,并且加密过后的Sec-WebSocket-Key
。
- Sec-WebSocket-Version表明客户端所使用的协议版本。
- Sec-WebSocket-Accept 表示服务器接受了客户端的请求
http请求完成后响应的状态码为101,表示切换了协议,说明WebSocket协议通过http协议来建立运输层的TCP连接,之后便与http协议无关了。
发送和接收数据
WebSocket 双端传输的是一个个数据帧。格式如下:
- FIN。
- 0:不是消息的最后一个分片;
- 1:是消息的最后一个分片;
占 1 bit,值对应的含义如下:
- RSV1 RSV2 RSV3
均占 1 bit,一般情况下值为
0
。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非 0,且值的含义由扩展进行定义。如果出现非零的值,但并没有采用 WebSocket 扩展,则连接出错。- Opcode
- %x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片;
- %x1:表示这是一个文本帧(text frame);
- %x2:表示这是一个二进制帧(binary frame);
- %x3-7:保留的操作代码,用于后续定义的非控制帧;
- %x8:表示连接断开,是一个控制帧(close);
- %x9:表示这是一个心跳请求(ping);
- %xA:表示这是一个心跳响应(pong);
- %xB-F:保留的操作代码,用于后续定义的控制帧;
占 4 bit,其值可以是
%x0
、%x1
、%x2
、%x3~7
、%x8
、%x9
、%xA
和 %xB~F
中的任何一个。值对应的含义如下:- Mask
占 1 bit,其值为
0
或 1
。值 0
表示要对数据进行掩码异或操作,反之亦然。- Payload length
- 0~125:数据的长度等于该值;
- 126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度;
- 127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。
占 7 bit 或 7+16 bit 或 7+64 bit,表示数据的长度,其值可以是
0~127
中的任何一个数。值对应的含义如下:- 掩码
掩码的作用并不是为了防止数据泄密,而是为了防止早期版本的协议中存在的代理缓存污染攻击(proxy cache poisoning attacks)问题。这里要注意的是从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
所有客户端发送到服务端的数据帧,Mask都是1。如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。
如果Mask是1,那么在Masking-key中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。
掩码算法:按位做循环异或运算,先对该位的索引取模来获得 Masking-key 中对应的值 x,然后对该位与 x 做异或,从而得到真实的 byte 数据。
掩码键是由客户端随机选择的32位值。
- Masking-key
0 or 4 bytes。客户端发送到服务器的所有帧通过一个包含在帧中的32位值来掩码。如果mask位设置为1,则该字段存在,如果mask位设置为0,则该字段缺失。
- Payload Data
双端接收到数据帧之后,可以根据上述几个数据帧组件的值对 Payload Data 进行处理或直接提取数据。
在双端建立 WebSocket 连接后,任何一端都可以给另一端发送消息,这里的消息指的就是数据帧。但平时我们输入或输出的信息都是“明文”,所以在消息发送前需要将“明文”通过一定的方法转换成数据帧。而在接收端,拿到数据帧后需要按照一定的规则将数据帧转换为”明文“。下图描述了双端收发
Hello, world
的主要流程:关闭通道阶段握手
服务端会定期给所有的客户端发送一个 opcode 为
%x9
的数据帧,这个数据帧被称为 Ping 帧。客户端在收到 Ping 帧时,必须回复一个 opcode 为 %xA
的数据帧(又称为 Pong 帧),否则服务端就可以主动断开连接。反之,如果服务端在发送 Ping 帧后能够得到客户端 Pong 帧的回应,就代表这个客户端是活跃的,不要断开连接。如果需要关闭连接,那么一端向另一端发送 opcode 为
%x8
的数据帧即可,这个数据帧被称为关闭帧。在收到这样一个帧时,另一个节点在响应中发送一个Close帧。发送一个控制帧之后,表示连接将被关闭,一个节点不会发送任何更多的数据;
接收一个控制帧之后,表示连接将被关闭,一个节点会丢弃任何收到的数据;
总结
WebSocket 有几个关键点:握手、数据与数据帧的转换、保持连接的 Ping 帧和 Pong 帧、主动关闭连接的关闭帧。
websocket协议规范的中文翻译如下:
websocket-protocol
shiqinfeng1 • Updated Aug 12, 2019