WebSocket

背景

现在,很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

握手协议

WebSocket是独立,建立在TCP上的协议。

HTTP的关系是,WebSocket第一次的握手请求是使用HTTP协议:

一个典型的握手请求请求:

1
2
3
4
5
6
7
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example.com
Origin: http://example.com
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13

服务端回应:

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example.com/

字段说明

  • Connection必须设置Upgrade,表示客户端希望连接升级。
  • Upgrade字段必须设置Websocket,表示希望升级到Websocket协议。
  • Sec-WebSocket-Key是随机的字符串,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个特殊字符串“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”,然后计算SHA-1摘要,之后进行BASE-64编码,将结果做为“Sec-WebSocket-Accept”头的值,返回给客户端。如此操作,可以尽量避免普通HTTP请求被误认为Websocket协议。
  • Sec-WebSocket-Version 表示支持的Websocket版本。RFC6455要求使用的版本是13,之前草案的版本均应当被弃用。
  • Origin字段是可选的,通常用来表示在浏览器中发起此Websocket连接所在的页面,类似于Referer。但是,于Referer不同的是,Origin只包含了协议和主机名称。
  • 其他一些定义在HTTP协议中的字段,如Cookie等,也可以在Websocket中使用。

客户端发送握手请求,要求服务端升级协议。服务端再回复HTTP/1.1 101 Switching Protocols。 自此双通道已搭建,两端使用发送序列化的数据帧传输数据

除此之外,WebSocket也规定了加密数据传输方法,允许使用TLS/SSL对通信进行加密,类似HTTPS。默认情况下,ws协议使用80端口进行普通连接,加密的TLS连接默认使用443端口。

数据帧

出于安全原因考虑。不管WebSocket协议是否基于TLS上,客户端发送的数据帧中必须包含掩码。

  1. 客户端向服务器传输的数据帧必须进行掩码处理:服务器若接收到未经过掩码处理的数据帧,则必须主动关闭连接。
  2. 服务器向客户端传输的数据帧一定不能进行掩码处理。客户端若接收到经过掩码处理的数据帧,则必须主动关闭连接。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0               1               2               3                <- byte
0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 0 1 2 3 4 5 6 7 <- bit
+-+-+-+-+-------+-+-------------+-----------------------------+
|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 ... |
+-------------------------------------------------------------+

FIN1 bit 指示这个是消息的最后片段。

RSV1,RSV2,RSV3各占1 bit 扩展协议非零值定义,一般为0。

Opcode操作码,占4 bit定义payload Data的意义。

  • %x0 代表一个继续帧
  • %x0 代表一个继续帧
  • %x1 代表一个文本帧
  • %x2 代表一个二进制帧
  • %x3-7 保留用于未来的非控制帧
  • %x8 代表连接关闭
  • %x9 代表ping
  • %xA 代表pong
  • %xB-F 保留用于未来的控制帧

Mask是否存在掩码标志位,占1 bit。若为1,则Masking-key存在掩码。客户端发送的数据帧中必须为1

Payload length负载数据长度。占7 bits,且最高有效位必须为0。所以7 bits只能表示0~127(2^7 - 1),若值为0~125则表示7 bits是有效负载数据长度。若值为126,则表示需要查看后16 bits。若值为127,则表示需要查看后面64 bits

Masking-key 客户端发送数据帧的掩码

payload Data 应用数据

掩码存在的必要性

掩码是由客户端随机选择的32位值。 客户端必须从允许的32位值集合中选择一个新的掩码。 掩码需要是不可预测的;因此,掩码键必须来自一个强大的熵源,且用于给定帧的掩码键必须不容易被服务器/代理预测用于后续帧的掩码键。

掩码键的不可预测性对防止恶意应用的作者选择出现在报文上的字节是必要的。

引用

The WebSocket Protocol
The WebSocket Protocol 中文版