WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。WebSocket通信协议于2011年被IETF定为标准RFC6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。它被设计用于替代传统的 HTTP 长轮询方法来实现实时通信,提供了更高效、低延迟的数据传输方式。
WebSocket原理
握手过程
WebSocket连接的建立需要经过一次握手过程。客户端与服务器之间首先建立一个HTTP连接,然后通过Upgrade请求头将连接协议升级为WebSocket。
客户端发起握手请求
客户端通过发送一个标准的HTTP请求来发起握手。这个请求与普通的HTTP请求类似,但包含了一些特定的头字段,以指示这是一个WebSocket握手请求。
GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
Origin: http://example.com
GET /ws HTTP/1.1
: 请求行,指定了要访问的URL(这里是/ws
)和HTTP版本。Host
: 标准的HTTP头,指定了服务器的域名或IP地址。Upgrade: websocket
: 请求头,指示客户端想要将连接升级到WebSocket协议。Connection: Upgrade
: 请求头,与Upgrade
头一起使用,表示客户端想要升级协议。Sec-WebSocket-Key
: 一个base64编码的随机值,服务器将使用这个值来构造一个响应,以证明它理解WebSocket协议。Sec-WebSocket-Version
: 指示客户端想要使用的WebSocket协议版本。Origin
: (可选)指示发起请求的源(在浏览器中通常是脚本发起的)。
服务器响应握手请求
服务器接收到握手请求后,如果它支持WebSocket协议,并且愿意接受这个连接,它会返回一个包含特定头字段的HTTP响应。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
HTTP/1.1 101 Switching Protocols
: 状态行,表示服务器同意切换协议。Upgrade: websocket
: 响应头,确认服务器将协议升级到WebSocket。Connection: Upgrade
: 响应头,确认服务器将连接升级。Sec-WebSocket-Accept
: 服务器使用客户端发送的Sec-WebSocket-Key
,按照特定规则(将这个值与一个固定的GUID"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
拼接,然后计算SHA-1散列,最后进行base64编码)生成的值。客户端会验证这个值以确认服务器确实理解WebSocket协议。
握手完成
一旦客户端收到 101 响应并且验证了 Sec-WebSocket-Accept 字段,它就知道握手成功了。此时,连接将从 HTTP 升级到 WebSocket 协议,并且客户端和服务器都可以开始发送和接收数据帧。
数据传输
握手完成后,客户端和服务器之间可以开始发送数据帧。WebSocket 支持两种类型的数据帧:文本帧和二进制帧。
- 文本帧:包含 UTF-8 编码的文本数据。
- 二进制帧:包含任意二进制数据。
除了数据帧之外,WebSocket 还支持控制帧,如关闭帧、ping 帧和 pong 帧。
- 关闭帧:用来关闭连接。
- ping 帧:客户端或服务器可以发送 ping 帧来检查连接是否仍然有效。
- pong 帧:接收 ping 帧的一方必须发送一个 pong 帧作为回应。
WebSocket通过心跳机制来维持连接。客户端和服务器在连接建立后,定期发送ping帧和pong帧,以确保对方在线。
数据帧格式
WebSocket的握手过程成功之后,会进行双向数据传输,这些数据以“帧”为单位进行发送和接收。
WebSocket 数据帧由一个固定长度的头部和一个可变长度的数据负载组成。
头部结构
第一个字节
第一个字节包含重要的标志和控制信息。FIN占1位,表示这是一个消息的最后一个分片,即消息结束标志。RSV1、RSV2、RSV3各占1位,通常用于扩展,默认为0。
操作码 (4 bits): 指定帧类型,常见的操作码有:
- 0x0: 续帧,属于多帧消息的一部分。
0x1
: 文本帧,包含 UTF-8 编码的文本数据。0x2
: 二进制帧,包含任意二进制数据。0x3-7
:保留未来的非控制帧使用。0x8
: 关闭帧,通知另一端关闭连接。0x9
: PING 帧,用于检测连接是否仍然有效。0xA
: PONG 帧,作为对 PING 帧的响应。0xB-F
:保留未来的控制帧使用。
第二个字节
掩码标志 (1 bit): 指示来自客户端的帧是否进行了掩码处理。所有来自客户端的帧都必须设置这个位为1。
负载长度 (7 bits): 指示负载数据的长度。根据7bits
具体值可到 64 bits
。
- 如果负载长度小于 126,则直接使用该字节的最后 7 位表示。
- 如果负载长度在 126 和 65535 之间,则使用接下来的两个字节(16 位整数)表示。
- 如果负载长度大于 65535,则使用接下来的八个字节(64 位整数)表示。
掩码键 (如果需要)
- 掩码键 (4 bytes): 当掩码标志被设置时,这四个字节用于解码客户端发送的数据负载。服务器发送的数据负载不需要掩码。
负载数据
根据负载长度字段指定的长度,包含实际的数据内容。
掩码处理
WebSocket协议要求所有来自客户端的帧都必须进行掩码处理,这是为了防止恶意脚本在浏览器中通过WebSocket连接注入数据。掩码处理是通过将每个数据字节与掩码键进行XOR操作来完成的。
掩码键是随机生成的,并且每个帧都是唯一的。服务器在接收到掩码帧时,必须使用掩码键来解掩码数据。