websocket握手
客户端发送
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
服务端返回
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
- Sec-WebSocket-Protocol 可以用来指定子协议
握手协议服务端需要校验
- 请求方法必须是GET,而且http版本至少为1.1
- 请求头部必须要host,upgrade(值必须为websocket),Connection(值必须为Upgrade),Sec-WebSocket-Key(值为随机生成的16字节随机数通过base64编码得到的),Sec-WebSocket-Version(值必须为13)
- 请求头部可能包含Sec-WebSocket-Protocol 用来指定子协议。该值形式为用逗号分割的按权重排序的子协议列表。
- 请求头部可能包含Sec-WebSocket-Extensions。
握手协议客户端需要校验
- 判断状态码,收到401状态码可能需要验证,收到3xx需要重定向
- 返回头部必须包含Upgrade且值必须为websocket
- 返回头部必须包含Connection且值必须为Upgrade
- 返回头部必须包含Sec-WebSocket-Accept且值为 base64(sha1(Sec-WebSocket-Key+“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”))
- 若返回头部包含Sec-WebSocket-Extensions,则需确保其值在客户端请求头部中包含
- 若返回头部包含Sec-WebSocket-Protocol,则需确保其值在客户端请求头部中包含
数据帧格式
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 ... |
+---------------------------------------------------------------+
字段含义 位数(bit)FIN表示这是消息的最后一个片段。第一个片段也有可能是最后一个片段1RSV1,RSV2,RSV3必须设置为0,除非扩展了非0值含义的扩展。如果收到了一个非0值但是没有扩展任何非0值的含义,接收终端必须断开WebSocket连接。每个1 bitOpcode
定义“有效负载数据”的解释。如果收到一个未知的操作码,接收终端必须断开WebSocket连接
| 编码 | 含义 |
|---|---|
| 0 | 持续帧 |
| 1 | 文本帧 |
| 2 | 二进制帧 |
| 3-7 | 预留给以后的非控制帧 |
| 8 | 连接关闭包 |
| 9 | ping包 |
| 10 | pong包 |
| 11-15 | 预留给以后的控制帧 |
4Mask mask标志位,定义“有效负载数据”是否添加掩码。如果设置为1,那么掩码的键值存在于Masking-Key中,根据5.3节描述,这个一般用于解码“有效负载数据”。所有的从客户端发送到服务端的帧都需要设置这个bit位为1。1Payload length
以字节为单位的“有效负载数据”长度,根据不同的情况分为以下几种
| 值范围 | 含义 |
|---|---|
| 0-125 | 负载数据的长度 |
| 126 | 接下来的2个bytes解释为16bit的无符号整形作为负载数据的长度 |
| 127 | 接下来的8个bytes解释为一个64bit的无符号整形(最高位的bit必须为0)作为负载数据的长度 |
7 bits, 7+16 bits, or 7+64 bitsMasking-Key所有从客户端发往服务端的数据帧都已经与一个包含在这一帧中的32 bit的掩码进行过了运算。如果mask标志位(1 bit)为1,那么这个字段存在,如果标志位为0,那么这个字段不存在0 or 4 bytesPayload data
“有效负载数据”是指“扩展数据”和“应用数据”。
| 类型 | 含义 | 字节 |
| Extension data | 除非协商过扩展,否则“扩展数据”长度为0 bytes。 | x bytes |
| Application data | 任意的“应用数据”,占用“扩展数据”后面的剩余所有字段。“应用数据”的长度等于有效负载长度减去“扩展应用”长度。 | y bytes |
(x+y) bytes
客户端掩码算法
掩码、反掩码操作都采用如下算法:
j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j
解释一下:
- _1)_original-octet-i:为原始数据的第 i 字节;
- _2)_transformed-octet-i:为转换后的数据的第 i 字节;
- _3)_masking-key-octet-j:为 mask key 第 j 字节。
状态码
当关闭一个连接时(如:在开始握手已经完成后,发送一个关闭帧),终端可能会说明关闭的原因。终端的这个原因的描述和终端应该采取的行动,在这个文档中都没有说明。这个文档提前定义了一些可能用于扩展、框架和终端应用的状态码和状态码范围。这些状态码和任何有关联的的文本消息在关闭帧中都是可选的
| 状态码 | 含义 |
|---|---|
| 1000 | 表示一个正常的关闭,意味着连接建立的目标已经完成了 |
| 1001 | 表示终端已经“走开”,例如服务器停机了或者在浏览器中离开了这个页面 |
| 1002 | 表示终端由于协议错误中止了连接 |
| 1003 | 表示终端由于收到了一个不支持的数据类型的数据(如终端只能理解文本数据,但是收到了一个二进制数据)从而关闭连接 |
| 1004 | 保留字段 |
| 1005 | 是一个保留值并且不能被终端当做一个关闭帧的状态码 |
| 1006 | 是一个保留值并且不能被终端当做一个关闭帧的状态码。这个状态码是为了给上层应用表示连接被异常关闭如没有发送或者接受一个关闭帧这种场景的使用而设计的 |
| 1007 | 表示终端因为收到了类型不连续的消息(如非 UTF-8 编码的文本消息)导致的连接关闭 |
| 1008 | 表示终端是因为收到了一个违反政策的消息导致的连接关闭 |
| 1009 | 表示终端由于收到了一个太大的消息无法进行处理从而关闭连接 |
| 1010 | 表示终端(客户端)因为预期与服务端协商一个或者多个扩展,但是服务端在 WebSocket 握手中没有响应这个导致的关闭。需要的扩展清单应该出现在关闭帧的原因(reason)字段中。 |
| 1011 | 内部服务器错误 |
| 1015 | 这个状态码是用于上层应用来表示连接失败是因为 TLS 握手失败(如服务端证书没有被验证过)导致的关闭的。 |
分片
一个分片的消息由起始帧(FIN为0,opcode非0),若干(0个或多个)帧(FIN为0,opcode为0),结束帧(FIN为1,opcode为0)。