前言
一.通用协议设计
bash
/*
+---------------------------------------------------------------+
| 魔数 2byte | 协议版本号 1byte | 序列化算法 1byte | 报文类型 1byte |
+---------------------------------------------------------------+
| 状态 1byte | 保留字段 4byte | 数据长度 4byte |
+---------------------------------------------------------------+
| 数据内容 (长度不定) | 校验字段 2byte |
+---------------------------------------------------------------+
*/
待补充
正文
二.WebSocket 协议
2.1 基础数据帧
术语 | 说明 | 大小 |
---|---|---|
FIN | 如果是 1,表示这是消息(message)的最后一个分片(fragment);如果是 0,表示不是是消息(message)的最后一个分片(fragment) | 1bit |
RSV1, RSV2, RSV3 | 一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非 0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错 | 每个1 bit |
Opcode | 操作代码,Opcode 的值决定了应该如何解析后续的数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该断开连接(fail the connection) | 4 bits |
Mask | 表示是否要对数据载荷进行掩码操作。 从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。 如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。 如果 Mask 是 1,那么在 Masking-key 中会定义一个掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask 都是 1。 | 1bit |
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 bits, 7+16 bits, 或者 7+64 bits |
Masking-key | 所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask 为 1,且携带了 4 字节的 Masking-key。如果 Mask 为 0,则没有 Masking-key。 备注:载荷数据的长度,不包括 mask key 的长度。 | 0 or 4 bytes |
Payload data | "负载数据"定义为"扩展数据"连接"应用数据"。 -Extension data: x byte "扩展数据"是 0 字节除非已经协商了一个扩展。任何扩展必须指定"扩展数据"的长度,或长度是如何计算的,以及扩展如何使用必须在打开阶段握手期间协商。如果存在,"扩展数据"包含在总负载长度中。 - Application data: y bytes 任意的"应用数据",占用"扩展数据"之后帧的剩余部分。"应用数据"的长度等于负载长度减去"扩展数据"长度。 | (x+y) bytes |
Opcode:
- %x0 代表一个继续帧
- %x1 代表一个文本帧
- %x2 代表一个二进制帧
- %x3-7 保留用于未来的非控制帧
- %x8 代表连接关闭
- %x9 代表 ping
- %xA 代表 pong
- %xB-F 保留用于未来的控制帧
2.2 数据帧另外一种表达方式
bash
ws-frame = frame-fin ; 1 bit in length
frame-rsv1 ; 1 bit in length
frame-rsv2 ; 1 bit in length
frame-rsv3 ; 1 bit in length
frame-opcode ; 4 bits in length
frame-masked ; 1 bit in length
frame-payload-length ; either 7, 7+16,
; or 7+64 bits in
; length
[ frame-masking-key ] ; 32 bits in length
frame-payload-data ; n*8 bits in
; length, where
; n >= 0
frame-fin = %x0 ; more frames of this message follow
/ %x1 ; final frame of this message
; 1 bit in length
frame-rsv1 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv2 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-rsv3 = %x0 / %x1
; 1 bit in length, MUST be 0 unless
; negotiated otherwise
frame-opcode = frame-opcode-non-control /
frame-opcode-control /
frame-opcode-cont
frame-opcode-cont = %x0 ; frame continuation
frame-opcode-non-control= %x1 ; text frame
/ %x2 ; binary frame
/ %x3-7
; 4 bits in length,
; reserved for further non-control frames
frame-opcode-control = %x8 ; connection close
/ %x9 ; ping
/ %xA ; pong
/ %xB-F ; reserved for further control
; frames
; 4 bits in length
frame-masked = %x0
; frame is not masked, no frame-masking-key
/ %x1
; frame is masked, frame-masking-key present
; 1 bit in length
frame-payload-length = ( %x00-7D )
/ ( %x7E frame-payload-length-16 )
/ ( %x7F frame-payload-length-63 )
; 7, 7+16, or 7+64 bits in length,
; respectively
frame-payload-length-16 = %x0000-FFFF ; 16 bits in length
frame-payload-length-63 = %x0000000000000000-7FFFFFFFFFFFFFFF
; 64 bits in length
frame-masking-key = 4( %x00-FF )
; present only if frame-masked is 1
; 32 bits in length
frame-payload-data = (frame-masked-extension-data
frame-masked-application-data)
; when frame-masked is 1
/ (frame-unmasked-extension-data
frame-unmasked-application-data)
; when frame-masked is 0
frame-masked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-masked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
frame-unmasked-extension-data = *( %x00-FF )
; reserved for future extensibility
; n*8 bits in length, where n >= 0
frame-unmasked-application-data = *( %x00-FF )
; n*8 bits in length, where n >= 0
2.3 WebSocket掩码的作用
WebSocket的掩码算法是一种数据加密方法,用于保护数据传输的安全性。这种算法通过异或运算对数据进行处理,以防止早期版本的协议中存在的代理缓存污染攻击等问题。具体来说,掩码算法的实现过程如下:
-
掩码的作用:掩码算法并不是为了防止数据泄密,而是为了防止代理缓存污染攻击等问题。它通过对数据进行异或运算,使得原始数据在传输过程中被改变,只有在接收端使用相同的掩码进行反向操作,才能还原出原始数据。
-
算法描述:对于每个需要发送的字节,它通过与掩码密钥进行异或运算来生成传输的数据。具体来说,对于原始数据中的每个字节original-octet-i,它首先计算j = i MOD 4来获取掩码密钥中的对应字节masking-key-octet-j,为mask key第j个字节。然后,将original-octet-i与masking-key-octet-j进行异或运算,得到的结果即为传输的数据
即:j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j。
bash
void umask(char *payload, int len, char *mask)
{
int i = 0;
for (i = 0; i < len; i++)
{
payload[i] ^= mask[i % 4];
}
}
- 示例:以客户端发送语音文件到服务端的场景为例,客户端首先发送txt消息文件名称,然后再发送bin消息二进制流数据。在这个过程中,客户端对需要发送的字符与掩码进行异或运算,生成用于网络传输的数据。例如,字符't'的ASCII值为116,与掩码14进行异或运算后,得到的结果用于传输。
待补充