Websocket 协议格式说明
最近整理项目中关于websocket中的部分,由于之前代码中websocket的功能不够完整,缺少了延续帧的数据拼接,以及mask的掩码功能。所以初步深入了一下websocket协议。
关于websocket的数据帧格式,官方文档提供了一个结构图
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
FIN:标识该数据帧是否是信息的最后一帧数据,如果为1则表明距 上一次 FIN为1 的一帧之间,这些数据是完整的一条信息。
RSV 1-3:默认为0,除非协商了扩展定义了非0的意义。如果接收到非0,且没有协商扩展,接收端必须使WebSocket连接失败。
-
Opcode:帧类型
0:Continuation Frame,表明该帧数据是一条完整信息的其中一帧
1:Text Frame ,这是一条文本数据
2:Binary Frame ,这是一条二进制流数据
3 - 7:为以后的非控制帧保留
8:Connection Close Frame,这是关闭websocket连接请求的数据帧
9:Ping Frame ,这是一条ping帧
A:Pong Frame,这是一条pong帧
B - F:为以后的控制帧保留
Mask:掩码,是否加密数据,为1则需要对根据一个随机的maskCode对数据进行一次位运算,一般客户端发送给服务端的数据需要用掩码加密,而服务端发给客户端的一般不需要
-
Payload:盛放该帧数据的长度,由于该字段占据7bit,所以最大为127,所以规定
- 如果数据长度<126,则直接将数据长度存在这7bit中
- 如果长度=126,则将数据长度存在接下来的2字节数据中(2字节长度可存最大无符号整数为 65535)
- 如果长度=127,则将数据长度存在接下来的8字节数据中
Extended payload length:接上个字段,这两个字段用来存放数据的长度
Masking-key:占据 0或4 Byte,上面的Mask字段为1,则需要生成4bit的随机值,为0则为空
-
Payload data: (x + y)字节,存放了该数据帧所有的数据。由(Extension data + Application data 构成)
- Extension data:x字节,保留参数,默认为0 Byte,除非协商了扩展。
- Application data:y字节,如果上面Extension data为0,没有协商拓展,则此字段存储了该帧所有的数据。
大小端
定义:对于一个由2个字节组成的16位整数,在内存中存储这两个字节有两种方法
- 一种是将低序字节存储在起始地址,这称为小端(little-endian)字节序;
- 另一种方法是将高序字节存储在起始地址,这称为大端(big-endian)字节序。
之所以关注这个问题是因为,在完善别人的Websocket协议时,当数据长度大于125的时候,我需要将真正的数据长度填充至2个字节或者8个字节的Extended payload length之中,由于采用的小端模式存储,导致服务端那边读取的数据长度一直不对,最后才发现是这个问题。
位运算
&: 与 当两个位都为1时,结果才为1
1. 清零 与一个各位都为零的数值相与,结果为零
2. 取一个数的指定位, 用另一个指定位上全为1,其余位上全为0的数与该数相与,则获得的新值就是该数的指定位
3. 判断奇偶 最未位为0则为偶数,为1则为奇数
由此 可以用if ((a & 1) == 0)代替if (a % 2 == 0)来判断a是不是偶数,因为
1的末位数为1
|: 或 当两个位都为0时,结果才为0
1. 对一个数的某些位设置为1, 用另一个指定位上全为1,其余位上全为0的数与该数相或,则获得的新值指定位上的位就为1,非指定位还是会保持不变。
^: 异或 当两个位相同则为0,相异则为1
1. 翻转指定位, 用另一个指定位上全为1,其余位上全为0的数与该数异或,则获得的新值置顶位与该数指定位上的值正好相反
2. 与0相异或值不会改变 1000 0001 ^ 0000 0000 = 1000 0001
3. 交换两个数
a = 1000 1001, b = 1101 0000
a ^= b; // a = 0101 1001
b ^= a; // b = 1000 1001
a ^= b; // a = 1101 0000
如上所示,a 与b进行两次异或后,得出的新值还是a。
~: 取反, 每一位上0 ——> 1, 1 ——> 0
该运算符的优先级比其他的要高!
<< 左移运算符 将一个数的二进制位上各个位往左移若干位(左边的位丢
弃,右边补0)
a = 1011 1010, a<< 2 = 1110 1000
若左移时舍弃的高位不包含1,则每左移一位,相当于该数乘以2。
即 0011 1010<< 2 = 1110 1000
即 0011 1010 = 58, 1110 1000 = 116
>> 右移运算符 将一个数的二进制位上各个位往右移若干位(右边的位丢
弃,正数左边补0,负数左边补1)
a = 1011 1010, a>> 2 = 0010 1110
操作数每右移一位,相当于该数除以2。
即 1011 1010 = 186
即 0010 1110 = 46
不同长度的数据进行位运算时,则按照右端对齐,左端补齐0或者1,正数或者无符号数补0,负数补1
最后
上篇文章立的flag这么快就被打脸了,这是2021年的第一篇,也希望今年有一个好的开始吧,再接再厉!