WebSocket协议
WebSocket是一种全双工(full-duplex)的双向通信技术,它依赖于单个套接字。使用WebSocket之后,HTTP连接升级为TCP长连接,可以被反复使用以传输数据。WebSocket连接可以在HTTP或者HTTPS之上启动。
WebSocket的出现,让B/S应用的实时性更好,因为服务器可以随时把数据推送到客户端,不需要客户端进行轮询。
WebSocket常常指代一套JavaScript的API,但它也作为一种网络协议(RFC 6455),本文主要探讨WebSocket协议的细节。
特性 | TCP | HTTP | WebSocket |
寻址方式 | IP地址+端口 | URL | URL |
并发传输 | 全双工 | 半双工 | 全双工 |
载荷格式 | 二进制流 | MIME报文 | 文本或者二进制消息 |
消息边界 | 无 | 有 | 有 |
面向连接 | 是 | 否 | 是 |
可以看到,WebSocket包含了消息边界规范,因而比TCP更加简单。使用TCP时,随着网络负载、延迟的变化,TCP报文如何分片是无法预测的,唯一的保证是每个字节的接收顺序和发送顺序一致。而使用WebSocket时,多字节的消息会完整、按序的到达。
所有WebSocket连接都是在HTTP连接升级产生的。 客户端打开HTTP连接时,发送类似下面的请求:
1 2 3 4 5 6 7 8 9 |
GET /h264src HTTP/1.1 Pragma: no-cache Cache-Control: no-cache Host: 192.168.0.89:9090 Origin: http://192.168.0.89:9090 Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: BHIuTA54YKc80CVB9sfaJw== Sec-WebSocket-Version: 13 |
此请求与普通HTTP请求没有太大差异,关键的不同就是Upgrade头,其取值为websocket,表示升级当前连接的协议为WebSocket。
如果服务器同意升级,则HTTP应答报文类似下面:
1 2 3 4 5 |
HTTP/1.1 101 Upgrade: websocket Connection: upgrade Sec-WebSocket-Accept: gJ0vp6zOXy4g/koag0FAJkBCwSU= Date: Wed, 20 Sep 2017 03:17:27 GMT |
101状态码的含义是切换协议,切换到的协议由Upgrade字段说明。Sec-WebSocket-Accept的取值根据Sec-WebSocket-Key推导,供客户端验证。
WebSocket关闭并不总是能正常进行,特别是在因特网或者其它不可考网络中进行通信的时候,底层TCP连接可能突然就断开。
当正常关闭WebSocket时,关闭行为的发起端发送特定的opcode=8的消息给对方,说明关闭的操作代码和原因。
关闭操作代码和原因作为载荷发送。关闭操作代码为16bit整数,关闭原因则是简短的UTF-8字符串。关闭代码如下表:
关闭代码 | 说明 |
1000 | Normal Close。正常关闭 |
1001 | Going Away。发起者正在关闭,并且不期望后续再发起连接。出现的原因例如服务器准备停机维护 |
1002 | Protocol Error。因为协议错误而关闭 |
1003 | Unacceptable Data Type。消息类型不支持 |
1004-1006 | 保留 |
1007 | Invalid Data。数据无效,例如错误编码的文本消息 |
1008 | Message Violates Policy。如果关闭原因不被其它关闭代码覆盖,或者你不希望暴露关闭原因给对方,使用此代码 |
1009 | Message Too Large。消息长度过大,无法处理 |
1010 | Extension Required。由客户端发送,如果服务器不支持客户端需要的扩展 |
1011 | Unexpected Condition。不可预知的原因导致应用程序无法继续处理连接 |
1015 | TLS Failure。在握手之前TLS处理失败,不要使用此代码 |
4000-4999 | 你可以自定义这些代码的用途 |
一旦握手完成,之后的通信均基于WebSocket报文。通信双方可以随时发送WebSocket报文,此所谓全双工。
报文在网络中以二进制形式表示,它包含一个报文头。报文头标记了不同帧(Frame)之间的边界并且包含了简单类型信息。1-N个帧组成完整的WebSocket消息,通常情况下,一个消息总是包含仅仅一个帧,因此,帧和消息这两个术语经常替换使用。
WebSocket帧的格式如下图:
一般取值0,除非要发送有多个帧组成的消息。
要发送由多个帧组成的消息,则需要把报文首位FIN置为0。依次发送完所有帧后,将FIN置为1,提示接收方所有帧已经发送完毕。
除非协商使用了某种WebSocket扩展,这3bit均设置为0
指定消息载荷的类型,对应第一字节的后4个bit:
opcode | 载荷类型 | 说明 |
1 | 文本 | 载荷为文本 |
2 | 二进制 | 载荷为字节 |
8 | 关闭连接 | 客户端或者服务器发起,关闭握手 |
9 | Ping | 客户端或者服务器发起,Ping消息 |
10 | Pong | 客户端或者服务器发起,Pong消息 |
从浏览器发送到服务器的WebSocket帧被掩码处理以混淆载荷内容。掩码的意图并非防止窃听,而是出于非一般性的安全考虑,以及增强和即有HTTP代理服务器的兼容性。
报文头第二字节第1位说明报文是否被掩码处理。WebSocket协议要求客户端对所有帧进行掩码处理,服务器收到的任何帧,都需要解除掩码后再进一步处理。
如果报文被掩码,在报文长度头字段之后,会有4字节的掩码键。
WebSocket使用可变bit数来标注帧的长度:
- 如果帧小于126字节,使用7bit标注长度
- 如果帧长度在126-216之间, 使用额外两个字节标注长度
- 如果帧长度在216以上,使用8字节标注长度
其中,第2、3种情况下,最初的7bit被填写为126或者127,作为指示标记。
文本消息的编码为UTF-8,此编码与7bit的ASCII兼容。需要注意UTF-8是WebSocket唯一支持的文本编码格式。
WebSocket协议支持高层协议、高层协议协商。这些高层协议被称为子协议(Subprotocols)。
要使用子协议,在握手时客户端发送HTTP头 Sec-WebSocket-Protocol,指明它支持的子协议列表。服务器的同名响应头则从列表中选择一个子协议。
Leave a Reply