1 背景
在 WebSocket 出现之前,为了实现推送技术,所用的技术都是轮询,轮询是指浏览器每隔一段时间向服务器发出 HTTP 请求,服务器再返回最新的数据给客户端
常见的轮询方式分为轮询与长轮询,它们的区别如下图所示:
这种传统的模式带来很明显的缺点,即浏览器需要不断向服务器发出请求,然而 HTTP 请求与响应可能会包含较长的头部,其中真正有效的数据可能只是很少的一部分,这样会消耗很多带宽资源。因此,HTML5 定义了 WebSocket 协议,能更好地节省服务器资源和带宽,并且能够更实时地进行通讯
2 什么是WebSocket
WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,它使得客户端和服务器之间的数据交换变得更加简单,只需要完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输,本质上一种计算机网络应用层的协议,用来弥补 HTTP 协议在持久通信能力上的不足
它有以下特点:
- 建立在 TCP 协议之上
- 与 HTTP 协议有着良好的兼容性:与 HTTP 和 HTTPS 使用相同的 TCP 端口,可以绕过大多数防火墙的限制,默认端口是 80(ws) 和 443(wss,运行在 TLS 之上),并且握手阶段采用 HTTP 协议
- 较少的控制开销:连接创建后,ws 客户端、服务端进行数据交换时,协议控制的数据包头部较小,而 HTTP 协议每次通信都需要携带完整的头部
- 可以发送文本,也可以发送二进制数据
- 没有同源限制,客户端可以与任意服务器通信
- 协议标识符是 ws,如果加密则为 wss,服务器网址就是 URL
- 支持扩展:ws 协议定义了扩展,用户可以扩展协议,或者实现自定义的子协议,比如支持自定义压缩算法
3 原理
3.1 建立连接
在 WebSocket 开始通信之前,通信双方需要先进行握手,WebSocket复用了 HTTP 的握手通道,即客户端通过 HTTP 请求与 WebSocket 服务端协商升级协议,协议升级完成后,后续的数据交换则遵照 WebSocket 的协议
利用 HTTP 完成握手有以下好处:
- 可以让 WebSocket 和 HTTP 基础设备兼容(运行在 80 端口 或 443 端口)
- 可以复用 HTTP 的 Upgrade 机制,完成升级协议的协商过程
3.2 交换数据
WebSocket 的每条消息可能会被切分成多个数据帧,发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完整的消息
以下是MDN 上的示例:
Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.
Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, newmessage containing text started)
Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)
Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!
在该示例中,客户端向服务器发送了两条消息,第一个消息在单个帧中发送,而第二个消息跨三个帧发送。当 WebSocket 的接收方收到一个数据帧时,会根据 FIN 字段值来判断是否收到消息的最后一个数据帧;利用 FIN 和 Opcode,我们就可以实现跨帧发送消息
其中 Opcode 表示操作码,它的可能值有:
- 0x1:传输数据是文本
- 0x2:传输数据是二进制数据
- 0x0:表示该帧是一个延续帧,这意味着服务器应该将帧的数据连接到从该客户端接收到的最后一个帧
- 0x3-7:保留的操作代码,用于后续定义的非控制帧
- 0x8:表示连接断开
- 0x9:表示这是一个心跳请求ping
- 0xA:表示这是一个心跳响应pong
- 0xB-F:保留的操作代码,用于后续定义的控制帧
具体的数据帧格式大概如下图所示,单位是比特:
- FIN:1 个比特,值为 1 表示这是消息的最后一帧,为 0 则不是
- RSV1, RSV2, RSV3:各占 1 个比特,一般情况下全为 0,非零值表示采用 WebSocket 扩展
- Mask: 1 个比特,表示是否要对数据进行掩码操作
- Payload length:数据负载的长度,单位是字节,为 7 位,或 7+16 位,或 1+64 位
- Masking-key:0 或 4 字节(32 位),所有从客户端传送到服务端的数据帧,数据都进行了掩码操作,Mask 为 1,且携带了 4 字节的 Masking-key;如果 Mask 为 0,则没有 Masking-key
- Payload data:具体数据
3.3 维持连接
使用 WebSocket 进行通信,可以通过建立心跳机制来判断连接正常没有断开或者服务是否可用,所谓心跳机制,就是定时发送一个数据包,让对方知道自己在线且正常工作,确保通信有效;如果对方无法响应,便可以弃用旧连接,发起新的连接了
需要重连的场景可能包括:网络问题或者机器故障导致连接断开、连接没断但不可用了或者连接对端的服务不可用
发送方 -> 接收方:ping
接收方 ->