WebSocket初探

WebSocket定义

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之前的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之前就直接可以创建持久性的连接,并进行双向数据传输。

WebSocket握手

客户端申请协议升级

首先,客户端发起协议升级请求,可以看到,采用的是标准的HTTP报文格式,且只支持GET方法。

GET / HTTP/1.1
Host: localhost:8080
Origin: http://127.0.0.1:3000
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw==
Sec-WebSocket-Protocol: soap, wamp
  • Connection: Upgrade:表示要升级协议
  • Upgrade: websocket:表示要升级到 websocket 协议。
  • Sec-WebSocket-Version: 13:表示 websocket 的版本。如果服务端不支持该版本,需要返回一个 Sec-WebSocket-Version header里面包含服务端支持的版本号。
  • Sec-WebSocket-Key:与后面服务端响应首部的 Sec-WebSocket-Accept 是配套的,提供基本的防护,比如恶意的连接,或者无意的连接。
  • Sec-WebSocket-Protocol:客户端想用使用的子协议

服务端响应协议升级

服务端返回内容如下,状态的代码101表示协议切换。到此完成协议升级,后续的数据交互都按照新的协议来。

HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=
Sec-WebSocket-Protocol: soap
  • Sec-WebSocket-Accept:根据客户端请求首部的 Sec-WebSocket-Key 计算出来。计算公式为:将 Sec-WebSocket-Key 跟 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 拼接。通过 SHA1 计算出摘要,并转成 base64 字符串。
  • Sec-WebSocket-Protocol:表示服务端从客户端提供的协议列表中,提供自身支持哪个

WebSocket数据传输

一个成功握手之后,客户端和服务器来回地传输数据,在本规范中提到的概念单位为“消息”。 在线路上,一个消息是由一个或多个帧的组成。 WebSocket 的消息并不一定对应于一个特定的网络层帧,可以作为一个可以被一个中间件合并或分解的片段消息。

数据帧

WebSocket 客户端、服务端通信的最小单位是 帧(frame),由 1 个或多个帧组成一条完整的 消息(message)。

发送端:将消息切割成多个帧,并发送给服务端;
接收端:接收消息帧,并将关联的帧重新组装成完整的消息;

数据帧格式概览

用于数据传输部分的报文格式是通过本节中详细描述的 ABNF 来描述。

下面给出了 WebSocket 数据帧的统一格式。熟悉 TCP/IP 协议的同学对这样的图应该不陌生。

从左到右,单位是比特。比如 FIN、RSV1各占据 1 比特,opcode占据 4 比特。

内容包括了标识、操作代码、掩码、数据、数据长度等。

  0                   1                   2                   3
  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
 +-+-+-+-+-------+-+-------------+-------------------------------+
 |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
 |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
 |N|V|V|V|       |S|             |   (if payload len==126/127)   |
 | |1|2|3|       |K|             |                               |
 +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
 |     Extended payload length continued, if payload len == 127  |
 + - - - - - - - - - - - - - - - +-------------------------------+
 |                               |Masking-key, if MASK set to 1  |
 +-------------------------------+-------------------------------+
 | Masking-key (continued)       |          Payload Data         |
 +-------------------------------- - - - - - - - - - - - - - - - +
 :                     Payload Data continued ...                :
 + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
 |                     Payload Data continued ...                |
 +---------------------------------------------------------------+

数据帧格式详解

针对前面的格式概览图,这里逐个字段进行讲解,如有不清楚之处,可参考协议规范,或留言交流。

FIN:1 个比特。
如果是 1,表示这是 消息(message)的最后一个分片(fragment),如果是 0,表示不是是 消息(message)的最后一个 分片(fragment)。

RSV1, RSV2, RSV3:各占 1 个比特。
一般情况下全为 0。当客户端、服务端协商采用 WebSocket 扩展时,这三个标志位可以非 0,且值的含义由扩展进行定义。如果出现非零的值,且并没有采用 WebSocket 扩展,连接出错。

Opcode: 4 个比特。
操作代码,Opcode 的值决定了应该如何解析后续的 数据载荷(data payload)。如果操作代码是不认识的,那么接收端应该 断开连接(fail the connection)。可选的操作代码如下:

%x0:表示一个延续帧。当 Opcode 为 0 时,表示本次数据传输采用了数据分片,当前收到的数据帧为其中一个数据分片。
%x1:表示这是一个文本帧(frame)
%x2:表示这是一个二进制帧(frame)
%x3-7:保留的操作代码,用于后续定义的非控制帧。
%x8:表示连接断开。
%x8:表示这是一个 ping 操作。
%xA:表示这是一个 pong 操作。
%xB-F:保留的操作代码,用于后续定义的控制帧。
Mask: 1 个比特。
表示是否要对数据载荷进行掩码操作。从客户端向服务端发送数据时,需要对数据进行掩码操作;从服务端向客户端发送数据时,不需要对数据进行掩码操作。

如果服务端接收到的数据没有进行过掩码操作,服务端需要断开连接。

如果 Mask 是 1,那么在 Masking-key 中会定义一个 掩码键(masking key),并用这个掩码键来对数据载荷进行反掩码。所有客户端发送到服务端的数据帧,Mask 都是 1。

Payload length:数据载荷的长度
单位是字节。为 7 位,或 7+16 位,或 1+64 位。

假设数 Payload length === x,如果

x 为 0~126:数据的长度为 x 字节。
x 为 126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度。
x 为 127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度。
此外,如果 payload length 占用了多个字节的话,payload length 的二进制表达采用 网络序(big endian,重要的位在前)。

Masking-key:0 或 4 字节(32 位)
所有从客户端传送到服务端的数据帧,数据载荷都进行了掩码操作,Mask 为 1,且携带了 4 字节的 Masking-key。如果 Mask 为 0,则没有 Masking-key。

备注:载荷数据的长度,不包括 mask key 的长度。

Payload data:(x+y) 字节
载荷数据:包括了扩展数据、应用数据。其中,扩展数据 x 字节,应用数据 y 字节。

扩展数据:如果没有协商使用扩展的话,扩展数据数据为 0 字节。所有的扩展都必须声明扩展数据的长度,或者可以如何计算出扩展数据的长度。此外,扩展如何使用必须在握手阶段就协商好。如果扩展数据存在,那么载荷数据长度必须将扩展数据的长度包含在内。

应用数据:任意的应用数据,在扩展数据之后(如果存在扩展数据),占据了数据帧剩余的位置。载荷数据长度 减去 扩展数据长度,就得到应用数据的长度。

掩码算法

掩码键(Masking-key)是由客户端挑选出来的 32 位的随机数。掩码操作不会影响数据载荷的长度。掩码、反掩码操作都采用如下算法:

首先,假设:

original-octet-i:为原始数据的第 i 字节。
transformed-octet-i:为转换后的数据的第 i 字节。
j:为i mod 4的结果。
masking-key-octet-j:为 mask key 第 j 字节。
算法描述为: original-octet-i 与 masking-key-octet-j 异或后,得到 transformed-octet-i。

j = i MOD 4
transformed-octet-i = original-octet-i XOR masking-key-octet-j

数据传递

一旦 WebSocket 客户端、服务端建立连接后,后续的操作都是基于数据帧的传递。

WebSocket 根据 opcode 来区分操作的类型。比如0x8表示断开连接,0x0-0x2 表示数据交互。

数据分片

WebSocket 的每条消息可能被切分成多个数据帧。当 WebSocket 的接收方收到一个数据帧时,会根据FIN的值来判断,是否已经收到消息的最后一个数据帧。

FIN=1 表示当前数据帧为消息的最后一个数据帧,此时接收方已经收到完整的消息,可以对消息进行处理。FIN=0,则接收方还需要继续监听接收其余的数据帧。

此外,opcode 在数据交换的场景下,表示的是数据的类型。0x01表示文本,0x02表示二进制。而0x00比较特殊,表示延续帧(continuation frame),顾名思义,就是完整消息对应的数据帧还没接收完。

连接保持 + 心跳

WebSocket 为了保持客户端、服务端的实时双向通信,需要确保客户端、服务端之间的 TCP 通道保持连接没有断开。然而,对于长时间没有数据往来的连接,如果依旧长时间保持着,可能会浪费包括的连接资源。

但不排除有些场景,客户端、服务端虽然长时间没有数据往来,但仍需要保持连接。这个时候,可以采用心跳来实现。

发送方 ->接收方:ping
接收方 ->发送方:pong
ping、pong 的操作,对应的是 WebSocket 的两个控制帧,opcode分别是 0x9、0xA。

关闭连接

一旦发送或接收到一个Close控制帧,这就是说,_WebSocket 关闭阶段握手已启动,且 WebSocket 连接处于 CLOSING 状态。

当底层TCP连接已关闭,这就是说 WebSocket连接已关闭 且 WebSocket 连接处于 CLOSED 状态。 如果 TCP 连接在 WebSocket 关闭阶段已经完成后被关闭,WebSocket连接被说成已经 完全地 关闭了。

如果WebSocket连接不能被建立,这就是说,WebSocket连接关闭,但不是 完全的 。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值