HTTP实战-websocket理论


1. 使用前端不断轮询后端这种策略的弊端

为了维护用户的体验,需要在1~2s的周期内不断地对后端进行轮询,这种策略带来的带宽额外开销是非常巨大的

同时不断的HTTP请求交互,还会给下游服务器造成非常大的负担,因为压根服务端里面就没有数据,那还轮询个啥?

2. 长轮询

HTTP请求发出后,一般会给服务预留一定的时间作为响应,比如3s,如果在规定的时间内没有返回,那么就认为是超时了,如果我们将HTTP的请求超时时间设置得很大,那么只要在这30s内收到了请求,那么就立即将数据返回到客户端,如果超时,那么就立即发送下一次请求

这样的话就减少了HTTP请求的个数,并且在大部分的情况下,用户都会在30s的区间内做扫码操作,所以响应是非常及时的

像这种发起一个请求,在较长时间内等待服务器响应的机制,就是所谓的长训轮机制。我们常用的消息队列 RocketMQ 中,消费者去取数据时,也用到了这种方式。

像这种,在用户毫无感知的情况下服务器将数据推送到服务端的技术就是服务器推送技术

一般来说的实现有短轮询(短时间内发起多次请求)长轮询(较长的时间内维护同一次请求)

3. WebSocket

假设现在的项目需求是

需要在一个时间周期内不断地向客户端推送信息,那么这时候就需要用到WebSocket技术了

TCP的特性是全双工的,它在同一时间内,双方都能够主动地向对方发送数据,这就是所谓的全双工技术

而现在使用最广泛的HTTP/1.1,也是基于TCP协议的,同一时间里,客户端和服务器只能有一方主动发数据,这就是所谓的半双工

所以,为了更好地支持这种场景,我们需要设计一个新的TCP协议,于是新的应用层协议WebSocket就出现了

4. WebSocket连接是怎么连接的?

首先,普通的网页端与服务端的请求正常情况下还是走HTTP请求的,但是当客户端想要升级为WebSocket的时候,那么就需要基于HTTP协议完成WebSocket协议的连接握手了,具体的过程是这样的:

首先在HTTP的头部添加一些特殊的header

Connection: Upgrade
Upgrade: WebSocket
Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg==\r\n

解析:

  • Connection:浏览器想要升级协议
  • Upgrade:升级成啥呢?升级成WebSocket协议
  • Sec-WebSocket-Key:随机生成的 base64 码(Sec-WebSocket-Key)

如果此时服务端是支持WebSocket协议的,那么就会走WebSocket的握手流程,同时根据客户端生成的base-64码,用某个公开的算法变成另一段字符串放在 HTTP 响应的 Sec-WebSocket-Accept 头里,同时带上101状态码,发回给浏览器。HTTP 的响应如下:

HTTP/1.1 101 Switching Protocols\r\n
Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY=\r\n
Upgrade: WebSocket\r\n
Connection: Upgrade\r\n

101指的是协议切换的状态码,当建立了websocket连接之后,在服务端返回可客户端的握手报文中就携带有此字段

之后,浏览器也用同样的公开算法base64码转成另一段字符串,如果这段字符串跟服务器传回来的字符串一致,那验证通过。

简述webSocket的握手流程

  • 首先,协议升级请求由客户端发出,然后在头部中携带如下字段
Connection: Upgrade
Upgrade: WebSocket
Sec-WebSocket-Key: xxx

然后发送到服务端,服务端如果支持这个协议的话,那么就会返回一个响应信息

HTTP/1.1 101 Switching Protocols\r\n
Sec-WebSocket-Accept: iBJKv/ALIW2DobfoA4dmr3JHBCY=\r\n
Upgrade: WebSocket\r\n
Connection: Upgrade\r\n

客户端在拿到这个信息之后,将这个Sec-WebSocket-Accept与自己之前发送的那个密文进行加密,然后对比,如果相同,那么就验证通过,连接成功建立

可以看出,WebSocket在握手阶段是借助了HTTP请求协议的,但是在连接建立之后就没有关系了。

5. WebSocket的消息格式

上面提到在完成协议升级之后,两端就会用webscoket的数据格式进行通信。

数据包在WebSocket中被叫做,我们来看下它的数据格式长什么样子。

opcode字段:这个是用来标志这是个什么类型的数据帧。比如。

  • 等于 1 ,是指text类型(string)的数据包
  • 等于 2 ,是二进制数据类型([]byte)的数据包
  • 等于 8 ,是关闭连接的信号

payload字段:存放的是我们真正想要传输的数据的长度,单位是字节。比如你要发送的数据是字符串"111",那它的长度就是3

WebSocket会用最开始的7bit做标志位。不管接下来的数据有多大,都先读最先的7个bit,根据它的取值决定还要不要再读个 16bit 或 64bit。

  • 如果最开始的7bit的值是 0~125,那么它就表示了 payload 全部长度,只读最开始的7个bit就完事了。

  • 如果是126(0x7E)。那它表示payload的长度范围在 126~65535 之间,接下来还需要再读16bit。这16bit会包含payload的真实长度。

  • 如果是127(0x7F)。那它表示payload的长度范围>=65536,接下来还需要再读64bit。这64bit会包含payload的长度。这能放2的64次方byte的数据,换算一下好多个TB,肯定够用了。

payload data字段:这里存放的就是真正要传输的数据,在知道了上面的payload长度后,就可以根据这个值去截取对应的数据。

大家有没有发现一个小细节,WebSocket的数据格式也是数据头(内含payload长度) + payload data 的形式。

这是因为 TCP 协议本身就是全双工,但直接使用纯裸TCP去传输数据,会有粘包的”问题”。为了解决这个问题,上层协议一般会用消息头+消息体的格式去重新包装要发的数据。

消息头里一般含有消息体的长度,通过这个长度可以去截取真正的消息体。


文章作者: 穿山甲
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 穿山甲 !
  目录