websocket
WebSocket 协议运行在TCP协议之上,与Http协议同属于应用层网络数据传输协议。WebSocket相比于Http协议最大的特点是:允许服务端主动向客户端推送数据。
WebSocket 协议诞生于2008年6月,并在2011年12月成为 RFC6455
https://www.rfc-editor.org/rfc/rfc6455.txt 国际标准,诞生之初被应用于HTML5相关规范中(移动互联网时代,也多应用于服务端向客户端推送消息的场景)。
WebSocket协议定义中客户端和服务端只需要完成一次握手,两者之间就可以建立持久性的连接,并进行双向数据传输。
与 HTTP 协议有着良好的兼容性:
WebSocket 默认端口是80,WebSocket Over SSL的默认端口是443;
数据格式比较轻量,性能开销小:
WebSocket数据帧格式相对简单,用于协议控制的数据包头部相对较小(在不包含扩展的情况下,对于服务端到客户端的内容,此头部大小只有2至10字节)。
数据实时性较高:
由于WebSocket是全双工的,所以服务端可以随时主动给客户端下发数据,相对于HTTP请求需要待客户端发起请求服务端才能响应,延迟明显更少,用户体验更高。
可以发送的载荷数据可以是文本数据,也可以是二进制数据;
WebSocket协议的数据载荷可以是普通的文本数据,也可以是二进制数据,数据量相对较大时,还可以分片多帧进行数据发送与接收。
协议标识
WebSocket 的协议标识符是ws(如果Over SSL,则为wss):
ws://example.com:80/some/path
wss://example.com:443/some/path
协议握手
WebSocket 协议握手复用了HTTP协议:
握手请求
客户端通过HTTP请求与WebSocket服务端协商升级协议到 Websocket 协议。
服务端通过HTTP响应数据回应客户端的升级请求,完成协议握手。
完成协议握手后,后续的数据交换则遵照 WebSocket 的协议进行。
客户端通过 HTTP/1.1 协议 GET 请求发起协议升级请求:
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
请求Header头域字段说明:
Connection: Upgrade:表示要升级协议;
Upgrade: websocket:表示要升级到websocket协议;
Sec-WebSocket-Version: 13:表示websocket的版本;
Sec-WebSocket-Key:与服务端响应首部的Sec-WebSocket-Accept是配套的,提供基本的防护,比如恶意或者无意的连接;
握手响应
服务端通过HTTP Response 中 101状态码 返回响应数据,完成协议握手:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
响应数据说明:
101 Switching Protocols:HTTP协议切换为WebSocket协议。
Sec-WebSocket-Accept:与请求数据中的Sec-WebSocket-Key数据对应,由请求数据中对应自己计算生成(计算方式后续详细说明)。
注:每个header都以\r\n结尾,并且最后一行加上一个额外的空行\r\n。
Sec-WebSocket-Accept计算
Sec-WebSocket-Accept根据客户端请求首部的Sec-WebSocket-Key计算出来。
计算公式为:
将Sec-WebSocket-Key跟258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接。
通过SHA1计算出摘要,并转成base64字符串。
// 伪代码举例
Base64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ) )
WebSocket客户端、服务端通信的最小单位是帧(frame),由1个或多个帧组成一条完整的消息(message)。
发送端:将消息切割成多个帧,发送给服务端;
服务端:接收消息帧,并将关联的帧重新组装成完整的消息数据;
FIN (Final):是否为最后一个分片(占1比特位)
如果是1,表示这是消息(message)的最后一个分片(fragment);
如果是0,表示不是是消息(message)的最后一个分片(fragment)。
RSV1, RSV2, RSV3 (Reserved):扩展字段(共占3比特位)
一般情况下全为0。当客户端、服务端协商采用WebSocket扩展时,这三个标志位可以非0,且值的含义由扩展进行定义。
对于 Reserved 字段,这里不做详细说明,如有扩展需求,可自行查阅相关国际标准:
RFC6455: WebSocket协议
https://www.rfc-editor.org/rfc/rfc6455.txt
Opcode:操作码(占4比特位)
Opcode的值决定了应该如何解析后续的数据载荷(data payload)。
可选的操作代码如下:
Opcode 含义
0x0 表示一个延续帧,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片
0x1 表示这是一个文本帧(frame)
0x2 表示这是一个二进制帧(frame)
0x3-7 保留的操作代码,用于后续定义的非控制帧
0x8 表示连接断开
0x9 表示这是一个ping操作
0xA 表示这是一个pong操作
0xB-F 保留的操作代码,用于后续定义的控制帧
Mask:掩码(占1比特位)
表示是否要对数据载荷进行掩码操作。
从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。
如果Mask是1,那么在Masking-key中会定义一个掩码键,并用这个掩码键来对数据载荷进行反掩码。
Payload length:数据载荷的长度,单位是字节(占 7、7+16 或7+64 比特位)
假设 Payload length 中前7 个比特位的值为x:
如果 x 为 0~125,则 x 即为载荷数据的有效长度;
如果 x 为 126,则后续16比特位代表一个16位的无符号整数,该无符号整数的值为载荷数据的有效长度;
如果 x 为 127,则后续64个比特位代表一个64位的无符号整数(最高位为0),该无符号整数的值为载荷数据的有效长度。
Masking-key:掩码键(占0或32比特位)
所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作(Mask值为1),会携带4字节的Masking-key。
对于 Masking-key 掩码算法,这里不做详细说明,如需了解,可自行查阅相关国际标准:
RFC6455: WebSocket协议
https://www.rfc-editor.org/rfc/rfc6455.txt
Payload data:载荷数据(占x+y字节)
载荷数据有两部分组成:扩展数据占 x 字节,应用数据占 y 字节。
扩展数据:如果没有协商使用扩展的话,扩展数据数据为0字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。
应用数据:任意的应用数据,在扩展数
心跳消息
WebSocket 是客户端与服务端的长链接,需要间隔一段发送Ping、Pong心跳,以抵挡运营商的Nat超时,来维持TCP连接不断开。
客户端需每隔一段时间(心跳间隔时间)发送一个 Ping 消息,到远端服务器侧;
服务端收到客户端的 Ping 消息后,需在10s内回复一个 Pong 消息;
早期的很多网站为具备数据推送能力,所在用的技术基本都是HTTP轮询。
轮询是由由客户端每隔一段时间(如每隔5s)向服务器发出HTTP请求,服务端接收到请求后向客户端返回最新的数据。
客户端的轮询方式一般为短轮询或长轮询。
短轮询:
一般是由客户端每隔一段时间(如每隔5s)向服务器发起一次普通 HTTP 请求。服务端查询当前接口是否有数据更新,若有数据更新则向客户端返回最新数据,若无则提示客户端无数据更新。
长轮询:
一般是由客户端向服务端发出一个设置较长网络超时时间的 HTTP 请求,并在Http连接超时前,不主动断开连接;待客户端超时或有数据返回后,再次建立一个同样的Http请求,重复以上过程。