WebSocket
是一种支持双向通信的网络协议。
双向通信:客户端(比如浏览器)可以向服务端发送消息,服务端也可以主动向客户端发送消息。
这样就实现了客户端和服务端的双向通信,那么上面所说的消息推送就比较容易实现了。
原先的 HTTP1.0/1.1
只能是客户端向服务端发送消息。
协议特点:
- 建立在 TCP 协议之上。
- WebSocket 协议是从 HTTP 协议升级而来。
- 与 HTTP 协议良好兼容新。默认端口是 80 和 443,握手阶段采用 HTTP 协议。
- 数据格式比较轻量,通信效率高,性能开销小。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务端通信。
- 协议标识符是 ws(如果加密,则为 wss),服务器网址就是 URL。
- 可以支持扩展,定了扩展协议。
- 保持连接状态,websocket 是一种有状态的协议,通信就可以省略部分状态信息。
- 实时性更强,因为是双向通信协议,所以服务端可以随时向客户端发送数据。
WebSocket 数据交换
数据帧格式
在 WebSocket
协议中,客户端与服务端数据交换的最小信息单位叫做帧(frame),由 1 个或多个帧按照次序组成一条完整的消息(message)。
数据传输的格式是由 ABNF 来描述的。
WebSocket 数据帧的统一格式如下图:
(https://www.rfc-editor.org/rfc/rfc6455.html#section-5.2 Base Framing Protocol)
上面图中名词解释:
名词 | 说明 | 大小 |
---|---|---|
FIN | 如果是 1,表示这是消息(message)的最后一个分片(fragment);如果是 0,表示不是是消息(message)的最后一个分片(fragment) | 1 个比特 |
RSV1, RSV2, RSV3 | 一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非 0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错 | 各占 1 个比特 |
opcode | 操作代码,Opcode 的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection) | 4 个比特 |
mask | 表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。 如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。 如果 Mask 是 1,那么在 Masking-key 中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask 都是 1。 | 1 个比特 |
Payload length | 数据载荷的长度,单位是字节。假设数 Payload length === x,如果: x 为 0~126:数据的长度为 x 字节。 x 为 126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度。 x 为 127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。 此外,如果 payload length 占用了多个字节的话,payload length 的二进制表达采用网络序(big endian,重要的位在前)。 | 为 7 位,或 7+16 位,或 1+64 位。 |
Masking-key | 所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask 为 1,且携带了 4 字节的 Masking-key。如果 Mask 为 0,则没有 Masking-key。 备注:载荷数据的长度,不包括 mask key 的长度。 | 0 或 4 字节(32 位 |
Payload data | 载荷数据:包括了扩展数据、应用数据。其中,扩展数据 x 字节,应用数据 y 字节。The "Payload data" is defined as "Extension data" concatenated with "Application data". 扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。 应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。 | (x+y) 字节 |
表中 opcode
操作码:
- %x0:表示一个延续帧(continuation frame)。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
- %x1:表示这是一个文本帧(frame),text frame
- %x2:表示这是一个二进制帧(frame),binary frame
- %x3-7:保留的操作代码,用于后续定义的非控制帧。
- %x8:表示连接断开。connection close
- %x9:表示这是一个 ping 操作。a ping
- %xA:表示这是一个 pong 操作。a pong
- %xB-F:保留的操作代码,用于后续定义的控制帧。
数据帧另外一种表达方式
客户端到服务端的掩码算法
https://www.rfc-editor.org/rfc/rfc6455.html#section-5.3 Client-to-Server Masking
掩码键(Masking-key
)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:
举例说明:
算法描述为: original-octet-i
与 masking-key-octet-j
异或后,得到 transformed-octet-i
。
数据分片
分片的目的:
- 有了消息分片,发送一个消息的时候,就可以发送未知大小的信息。如果消息不能被分片,那么就不得不缓冲整个消息,以便计算长度。而有了分片就可以选择合适大小缓冲区来缓冲分片。
- 第二个目的是可以使用多路复用。
WebSocket 的每条消息(message)可能被切分为多个数据帧。
当 WebSocket 的接收方接收到一个数据帧时,会根据 FIN 值来判断是否收到消息的最后一个数据帧。
从上图可以看出,FIN = 1 时,表示为消息的最后一个数据帧;FIN = 0 时,则不是消息的最后一个数据帧,接收方还要继续监听接收剩余数据帧。
opcode
表示数据传输的类型,0x01
表示文本类型的数据;0x02
表示二进制类型的数据;0x00
比较特殊,表示延续帧(continuation frame),意思就是完整数据对应的数据帧还没有接收完。
更多分片内容请看这里:https://www.rfc-editor.org/rfc/rfc6455.html#section-5.4
消息分片example:
五:怎么保持连接
在第二小结中我们介绍了 websocket 的特点,其中有一个是保持连接状态。
websocket 是建立在 tcp 之上,那也就是客户端与服务端的 tcp 通道要保持连接不断开。
怎么保持呢?可以用心跳来实现。
其实 websocket 协议早就想到了,它的帧数据格式中有一个字段 opcode
,定义了 2 种类型操作, ping
和 pong
,opcode
分别是 0x9
、0xA
。
说明:对于长时间没有数据往来的连接,如果依旧长时间保持连接的状态,那么就会浪费连接资源。
[完]