解决的问题
基于浏览器的机制,实现客户端与服务端的双向通信.
协议概述
- 来自客户端握手
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
2.来自服务端的握手
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
// 可选的头,表示允许的通过的客户端
Sec-WebSocket-Protocol: chat
以上,头顺序无所谓.
一旦客户端和服务器都发送了握手信号,如果握手成功,数据传输部分启动。这是双方沟通的渠道,独立于另一方,可随意发送数据。
服务器的响应,不是随意的,需要遵循一定的规则 请参考RFC 文档 第 6/7页:
- 获取客户端请求的
Sec-Weboscket-Key
字段值,去除收尾空白字符- 与全球唯一标识符拼接
258EAFA5-E914-47DA-95CA-C5AB0DC85B11
sha1
加密(短格式)- base64 加密
上述结果得出的值即是服务端返回给客户端握手的 Sec-Websocket-Accept
头字段值.
关闭链接
接收到一个 0x8
(opencode字段)控制帧后,链接也许立即断开,也许在接收完剩下的数据后断开。
- 可以有消息体,指明消息原因,可作为日志进行记录。
- 应用发送关闭帧后必须不在发送更多数据帧。
- 如果一个端点接受到一个关闭帧且先前没有发送关闭帧,则必须发送一个关闭帧。
- 端点在接受到关闭帧后,可以延迟响应关闭帧,继续发送或接受数据帧,但不保证一个已经发送关闭帧的端点继续处理数据。
- 发送并接收了关闭帧的端点,被认为是关闭了
websocket
连接,其必须关闭底层的TCP
连接。
设计理念
基于框架而不是基于流/文本或二进制帧.
链接要求
针对客户端要求
- 握手必须是一个有效的 HTTP 请求
- 请求的方法必须为
GET
,且HTTP
版本必须是 1.1 - 请求的
REQUEST-URI
必须符合文档规定的要求(详情查看 Page 13) - 请求必须包含
Host
头 - 请求必须包含
Upgrade: websocket
头,值必须为websocket
- 请求必须包含
Connection: Upgrade
头,值必须为Upgrade
- 请求必须包含
Sec-WebSocket-Key
头 - 请求必须包含
Sec-WebSocket-Version: 13
头,值必须为13
- 请求必须包含
Origin
头 - 请求可能包含
Sec-WebSocket-Protocol
头,规定子协议 - 请求可能包含
Sec-WebSocket-Extensions
,规定协议扩展 - 请求可能包含其他字段,如
cookie
等
不符合上述要求的服务器响应,客户端都会断开链接.
- 如果响应不包含
Sec-WebSocket-Protocol
中指定的子协议,客户端断开 - 如果响应
HTTP/1.1 101 Switching Protocols
状态码不是101
,客户端断开
针对服务端要求
- 如果请求是
HTTP/1.1
或更高的GET
请求,包含REQUEST-URI
则应正确地按照文档要求进行解析. - 必须验证 Host 字段
Upgrade
头字段值必须是大小写不敏感的websocket
Sec-WebSocket-key
d 解码时长度为16Byte
Sec-WebSocket-Version
值必须是13
Host
如果没有被包含,则链接不应该被解释为浏览器发起的行为Sec-WebSocket-Protocol
中列出的客户端请求的子协议,服务端应按照优先顺序排列,响应- 任选的其他字段
响应要求:
- 验证
Origin
字段,如果不符合要求的请求则返回适当的错误代码(例如:403) Sec-WebSocket-Key
值是一个base64
加密后的值,服务端不需要对其进行解码,而仅是用来创建服务器的握手.- 验证
Sec-WebSocket-Version
值,如果不是13
,则返回一个适当的错误代码(例如:HTTP/1.1 426 Upgrade Required
) - 资源名验证
- 子协议验证
- extensions 验证
如果通过了上述验证,则服务器表示接受该链接.那么起响应必须符合以下要求详情查看 Page 23:
- 必须,状态行
HTTP/1.1 101 Switching Protocols
- 必须,协议升级头
Upgrade: websocket
- 必须,表示连接升级的头字段
Connection: Upgrade
- 必须,
Sec-WebSocket-Accept
头字段,详情请查阅 协议概述 部分 - 可选:
Sec-WebSocket-Protocols
头部
完整的响应代码如下(严格按照如下格式响应!!头部顺序无所谓!关键是后面的换行符注意了!严格控制数量!):
HTTP/1.1 101 Switching Protocols\r\n
Connection: Upgrade\r\n
Upgrade: websocket\r\n
Sec-WebSocket-Accept: 3nlEzv+LqVBYnTHclAqtk62uOTQ=\r\n
// 下面这个头字段为可选字段
Sec-WebSocket-Protocols: chat\r\n\r\n
基本框架协议
数据传输部分对 位 进行了分组!!由于是在bit
层面上进行的数据封装,所以如果直接取出的话,获取到的将是处理后的数据,需要解密。下图是传输数据格式:
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 ... |
+---------------------------------------------------------------+
1. 特殊名词含义介绍
- 1bit,FIN
- 每个 1bit, RSV1、RSV2、RSV3
-
4bit,opcode(以下定义在ABNF中)
- %x0 连续帧
- %x1 文本帧
- %x2 二进制帧
- %x3 - %x7 保留帧
- %x8 链接关闭
- %x9 ping
- %xA pong
- %xB-F 保留的控制帧
- 以上表示的都是 16 进制数值
-
1bit, mask
- 客户端发送给服务端的数据都需要设置为 1
- 也就是说数据都是经过掩码处理过的
-
7bit、7 + 16bit、7 + 64bit,Payload length 具体范围请参阅 RFC 文档(Page 31)
payloadData的长度:7位,7+16位,7+64位,(超过16位和64位包长度,需要考虑网络字节序和本地字节序转换)
如果其值在0-125,则是payload的真实长度。
如果值是126,则后面2个字节形成的16位无符号整型数的值是payload的真实长度。
如果值是127,则后面8个字节形成的64位无符号整型数的值是payload的真实长度 -
0/4 byte, masking-key
- 客户端发送给服务端的数据都是经过掩码处理的,长度为 32bit
- 服务端发送给客户端的数据都是未经过掩码处理的,长度为 0bit
-
x + y Byte, Payload Data
- 有效载荷数据
-
x Byte, Extension Data
- 扩展数据
-
y Byte, Application Data
- 应用数据
消息分片
分片目的
消息分片的主要目的是允许消息开始但不必缓冲整个消息时,发送一个未知大小的消息;未分片的消息需要缓冲整个消息,以便获取消息大小;
分片要求:
- 首个分片 Fin = 0,opcode != 0x0,其后跟随多个 Fin = 0,opcode = 0x0的分片,终止于 Fin = 1,opcode = 0x0的片段
- 扩展数据可能发生在分片中的任意一个分片中
- 控制帧可能被注入到分片消息的中间,控制帧本身必须不被分割
- 消息分片必须按照发送者发送顺序交付给收件人
- 片段中的一个消息必须不能与片段中的另一个消息交替,除非已协商了一个能解释交替的扩展。
- websocket服务器应能够处理分片消息中间的控制帧
- 一个发送者可以为非控制消息(非控制帧)创建任何大小的片段
- 不能处理控制帧
- 如果使用了任何保留的位值且这些值的意思对中间件是未知的,一个中间件必须不改变一个消息的分片。
- 在一个连接上下文中,已经协商了扩展且中间件不知道协商的扩展的语义,一个中间件必须不改变任何消息的分片。同样,没有看见WebSocket握手(且没被通知有关它的内容)、导致一个WebSocket连接的一个中间件,必须不改变这个链接的任何消息的分片。
- 由于这些规则,一个消息的所有分片是相同类型,以第一个片段的操作码设置。因为控制帧不能被分片,用于一个消息中的所有分片的类型必须或者是文本、或者二进制、或者一个保留的操作码。