websocket协议简析
websocket协议是基于tcp的网络协议,实现了浏览器与客户端的全双工通信,与http协议不同的是,它允许服务器主动推送消息给客户端。这样便可以取代原来低效的轮询。
背景
在websocket出现之前,客户端获取服务器消息,需要通过不停的轮询。有如下缺陷:
1)服务器被迫为每个客户端使用许多不同的底层TCP连接:一个用于向客户端发送信息,其它用于接收每个传入消息。
2)http协议有额外的开销,每个消息都有HTTP头。
3)客户端需要通过映射来维护传出连接和传人连接用以追踪响应。
Websocket协议的出现正是弥补了上述缺陷,实现全双工通信,允许服务器主动推送消息给客户端。
协议分析
websocket协议分为两部分,一是握手建立连接;二是数据传输。
建立连接;
websocket的连接建立是基于http协议的。
请求报文示例:
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
下面来分析一下上述报文:
WebSocket借用http请求进行握手,相比正常的http请求,多了一些内容。其中,
HTTP/1.1 101表示websocket连接已成功建立,其他任何code表示连接建立失败。
Upgrade: websocket
Connection: Upgrade
表示希望将http协议升级到Websocket协议。
Sec-WebSocket-Key是浏览器随机生成的base64 encode的值,用来询问服务器是否是支持WebSocket。
服务器返回
Upgrade: websocket
Connection: Upgrade
告诉浏览器即将升级的是Websocket协议
Sec-WebSocket-Accept是将请求包"Sec-WebSocket-Key"的值,与"258EAFA5-E914-47DA-95CA-C5AB0DC85B11″这个字符串进行拼接,然后对拼接后的字符串进行sha-1运算,再进行base64编码得到的。用来说明自己是WebSocket助理服务器。
Sec-WebSocket-Version是WebSocket协议版本号。
若请求报文中,Sec-WebSocket-Version: 25,服务端可能的响应会是
HTTP/1.1 400 Bad Request ... Sec-WebSocket-Version: 13, 8, 7
此时客户端将会重新进行握手,并将版本修改为 Sec-WebSocket-Version: 13
数据传输:
websocket协议中数据是通过一系列的帧来传输。出于安全性考虑,所有客户端发往服务器的数据帧需要掩码,若服务器收到未掩码的数据帧将会主动断开连接;所有服务器发往客户端的数据帧不能掩码,若客户端收到掩码的数据帧将会主断开连接。
数据帧的定义:
FIN: 1 bit --是否是消息的最后一个数据帧。一个消息可以通过一个或多个数据帧发送,第一个帧也可能是最后一个帧。
RSV1, RSV2, RSV3: 1 bit each-- 保留字段,如果有自定义的扩展数据时使用。当没有扩展数据时,若保留字段非0,则会导致连接失败。
Opcode: 4 bits--说明数据的类型。eg:%x1表示文本帧;%x2表示二进制帧;%x8表示关闭连接帧;%x9表示ping帧;%xA 表示 pong帧。从此可以看出,目前websocket支持的传输类型包含文本,二进制和ping/pong。
Mask: 1 bit--是否掩码。(规则上文已述)
Payload length: 7 bits, 7+16 bits, or 7+64 bits--数据长度
Masking-key: 0 or 4 bytes--掩码的key(若mask is 1)
Payload data: (x+y) bytes-- 数据,包含扩展数据和应用数据。
Extension data: x bytes--私有定制协议。
Application data: y bytes--传输的数据
示例
o 非掩码的Hello: * 0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f
注解:
0x81 -->10000001
-->Fin=1(最后帧);RSV=000;Opcode=0001(文本帧)
0x05 -->00000101
--> mask=0(不掩码);Payload length=0000101(5个字符)
0x48 0x65 0x6c 0x6c 0x6f -->Hello
o掩码的Hello: * 0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58 (contains "Hello")
o多帧不掩码的Hello * 0x01 0x03 0x48 0x65 0x6c (contains "Hel")
0x80 0x02 0x6c 0x6f (contains "lo")
256字节二进制非掩码数据* 0x82 0x7E 0x0100 [256 bytes of binary data]
64KiB单帧二进制非掩码数据
0x82 0x7F 0x0000000000010000 [65536 bytes of binary data]
数据发送规则:
1)保证连接状态为open。
2)不知大小或太大的数据,会分为一系列的帧来发送。
3)数据发送帧中必须标明数据类型
4)最后一个数据帧必须将Fin设置为1
5)客户端发送的数据必须掩码
6)扩展数据必须有定义
7)数据必须通过下行连接发送。