WebSocket协议详解
- 一 、WebSocket 诞生背景
- 二、WebSocket 特点
- 三、WebSocket 的握手环节
- 四、WebSokect 的数据格式
- 1、 第一个字节
- 2、第二个字节
- 3、Masking-key
- 4、playload Data
- 5、一些注意细节
WebSocket 的官方文档
WebSocket 的中文文档(非官方)
一 、WebSocket 诞生背景
在互联网早期,HTTP(超文本传输协议)因其简单易用和功能强大而广受欢迎,成为了互联网通信的基石。然而,随着互联网的快速发展和网络安全、隐私保护需求的日益增长,HTTP逐渐显露出一些局限性,比如缺乏数据加密、身份验证和会话管理等安全特性。为了克服这些限制,HTTP的扩展和替代方案应运而生,其中最著名的就是HTTPS(安全超文本传输协议)。
HTTPS 解决了数据安全的问题,但是 HTTP 协议还有其他的缺陷:HTTP链接的半双工的,而且通信只能由客户端发起,服务端无法将数据主动推送给客户端
为了解决这个问题WebSocket
协议出现了(2008年),并在2011年成为国际标准。
扩展:
- 在早期没有
WebSocket
时,HTTP
协议为了实现推送技术,所用的技术都是轮询,由浏览器每隔一段时间向服务器发出 HTTP请求,然后服务器返回最新的数据给客户端。- 轮询方式分为轮询与长轮询,具体的细节可以参考这篇文章:轮询与长轮询
二、WebSocket 特点
WebSocket
特点:
-
WebSocket
是基于TCP
协议的。 -
WebSocket
链接是全双工的,客户端和服务端都可以主动发送数据给对方。 -
WebSocket
的协议标识符是ws
(如果加密,则为wss
),服务器网址就是URL
。
// http协议
http://example.com
https://example.com
// WebSocket协议
ws://example.com
wss://example.com
-
WebSocket
与HTTP
协议有着良好的兼容性。默认端口也是80和443(wss)。 -
WebSocket
在连接建立阶段需要进行一次 HTTP 握手,在握手完毕以后会从HTTP协议升级到WebSocket
协议 -
WebSocket
数据格式比较轻量,性能开销小,通信高效,发送的数据可以发送文本,也可以是二进制数据。
三、WebSocket 的握手环节
首先Websocket
是借用了HTTP的协议来完成握手的。
我们来看个典型的 Websocket 握手
- 客户端
GET / HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13
Origin: http://example.com
这是一段经典的HTTP格式的报文,我来讲解一下这里面一些重要字段的作用:
-
Upgrade
: 这个头部用于表明客户端希望服务器升级当前的连接到一个更高版本的协议。由于这里我们要使用WebSocket
所以这里我们设置为"websocket"
。 -
Connection
: 这个头部用来指定当前连接应该被升级,并且通常与Upgrade
头部一起使用。它的值通常设置为"Upgrade"
,表示客户端请求将连接升级到由Upgrade
头部指定的新协议。 -
Sec-WebSocket-Key
: 这个字段不是HTTP的标准字段,而是WebSocket
协议特定的字段。它是在WebSocket
握手过程中使用的,这个字段包含一个随机生成的字符串,用于生成最终的握手密钥,验证客户端和服务器之间的连接安全。 -
Sec-WebSocket-Version
: 这个字段也不是HTTP的标准字段,它用来指明WebSocket
的协议版本。
- 服务端
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
这里的 101 Switching Protocols
状态码表示服务器准备切换到WebSocke
t协议。Upgrade
和 Connection
头部再次出现以确认协议升级。
Sec-WebSocket-Accept
是服务器根据客户端的Sec-WebSocket-Key
计算出来的值,这些字段确保了WebSocket
连接的安全性。
这里是具体的步骤:
- 客户端生成一个随机字符串,并将其放在
Sec-WebSocket-Key
头部中。- 客户端将此字符串发送给服务器。
- 服务器接收到此字符串后,将其与
'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'
这个固定的 GUID 结合,并进行SHA-1
哈希运算。- 服务器将得到的哈希值进行
Base64
编码,并将结果放在Sec-WebSocket-Accept
头部中返回给客户端。- 客户端验证
Sec-WebSocket-Accept
中的值是否正确,以确认握手成功。
四、WebSokect 的数据格式
握手成功以后,双方通信都会采用WebSocket
进行通信,所以接下来让我们一起来看一看WebSokect 的数据格式:
1、 第一个字节
- FIN: 占1bit
- FIN=1:表示当前帧是消息的最后一部分,即该帧之后没有其他帧属于同一消息。这意味着当接收方收到FIN=1的帧时,可以认为整个消息已经完整接收,无需再等待后续帧。
- FIN=0:表示当前帧不是消息的最后一部分,即该帧之后还有更多的帧属于同一消息。接收方在收到FIN=0的帧时,需要继续接收后续的帧,直到遇到FIN=1的帧为止。
当消息较大,无法在一个帧中完全传输时,就需要进行分片处理。在这种情况下,除了最后一个帧外,其他所有帧的FIN字段都将被设置为0,以指示这些帧只是消息的一部分。而最后一个帧的FIN字段将被设置为1,以指示消息的结束。这种方式确保了接收方能够正确地重组分片消息,并恢复出原始的消息内容。
(这里类似与IP层的分片)
-
RSV1, RSV2, RSV3: 各占1bit, 一般情况下全为0, 这些字段与Websocket拓展有关, 如果出现非零的值且没有采用
WebSocket
拓展, 会导致连接出错。 -
Opcode: 占4bit,描述了
WebSocket
有关的一些操作指示- 0x0: 表示本次数据传输采用了数据分片, 当前数据帧为其中一个数据分片
- 0x1: 表示这是一个文本帧
- 0x2: 表示这是一个二进制帧
- 0x3-7: 保留的操作代码, 用于后续定义的非控制帧
- 0x8: 表示连接断开
- 0x9: 表示这是一个心跳请求(ping)
- 0xA: 表示这是一个心跳响应(pong)
- 0xB-F: 保留的操作代码, 用于后续定义的非控制帧
ping
和pong
这两个标志和WebSocket
的心跳机制有关。
- 为了保持 WebSocket 稳定的长连接,在连接建立之后,服务器和客户端之间通过心跳包来保持连接状态,以防止连接因为长时间没有数据传输而被切断。
- 这两个是一种特殊的数据包,不包含任何实际数据,仅用来维持连接状态,是一个空数据帧定期发送,确保链接仍然有效,避免长时间没有数据传输而被中断如果一段时间内没有收到对方的心跳包,就可以认为连接已经断开。
2、第二个字节
-
Mask
: 占1bit- 0表示不对数据载荷进行掩码异或操作
- 1表示对数据载荷进行掩码异或操作。
-
Payload length
: 表示的是有效载荷的长度,这个字段的长度的可变的,最小占据7个bit位,或7+16或7+64bit- 0~125: 数据长度在此区间内时,
Payload Length
是7 个bit 位。 - 126: 数据长度等于此值时,
Payload Length
是7 + 16 个bit 位。 - 127: 数据长度大于等于此值时,
Payload Length
是7 + 16 + 48个bit 位。
- 0~125: 数据长度在此区间内时,
3、Masking-key
Masking-key掩码值,如果Mask字段被设置为1,那么Masking-key
就占用4
bytes,否则不存该字段。这个字段主要用来对数据进行加密。
4、playload Data
payload data
: 载荷数据,如果没有设置Mask字段,这里的数据可以直接使用,如果设置了Mask字段,这里的数据还需要解除掩码之后才可以直接使用。
5、一些注意细节
-
WebSocket
协议要求客户端所发送的帧必须掩码,掩码的密钥是一个32位的随机值。所有数据都需要与掩码做一次异或运算。帧头在第二个字节的第一位表示该帧是否使用了掩码。 -
WebSocket
服务器接收的每个载荷在处理之前首先需要处理掩码,解除掩码之后,服务器将得到原始消息内容。 -
在
WebSocket
协议中,当服务器收到一个没有掩码的帧时,服务器必须丢弃这个消息,并且关闭服务器连接。同时服务器端给客户端发送的所有帧都是不含有掩码的,如果客户端检测到掩码的帧时,也一样必须丢弃该报文并且关闭连接。 -
根据上面的数据格式可以知道最小的
WebSocket
数据报文是两个字节的数据,不含有任何有效载荷(心跳包即使如此)。