使用 HTTP 不断轮询
3.9 既然有 HTTP 协议,为什么 还要有 WebSocket ?
使用 HTTP 不断轮询
其实问题的痛点在于,怎么样才能在用⼾不做任何 操作的情况下,网⻚能收到消息并发生变更。
最常⻅的解决方案是,网⻚的前端代码里不断定时发 HTTP 请求到服务器, 服务器收到请求后给
客⼾端响应消息。
这其实时一种「伪」服务器推的形式 。
它其实并不是服务器主动发消息到客⼾端,而是客⼾端自己不断偷偷 请求服务器, 只是用⼾无感
知而已。
用这种方式的场景也有很多,最常⻅的就是扫码登录。
比如,某信公众号平台,登录⻚面二维码出现之后,前端网⻚根本不知道用⼾扫没扫,于是不断
去向后端服务器询问,看有没有人扫过这 个码。而且是以大概 1 到 2 秒的间隔 去不断发出请求,
这样可以保 证用⼾在扫码后能在 1 到 2 秒内得到及时的反馈,不至于等太久。
使用HTTP 定时轮询
但这样,会有两个 比较明显 的问题:
当你打开 F12 ⻚面时,你会 发现满屏的 HTTP 请求。虽然很小,但这其实也消耗带宽,同时也
会增加下游服务器的负担。
最坏情况下,用⼾在扫码后,需要等个 1~2 秒,正好才触发下一次 HTTP 请求,然后才跳转⻚
面,用⼾会感到明显 的卡顿。
使用起来的体验就是,二维码出现后,手机扫一扫,然后在手机上点个确认,这时候卡顿等个 1~2
秒,⻚面才跳转。

有,而且实现起来成本还非常低。
⻓轮询
我们知道,HTTP 请求发出后,一般会给服务器留一定的时间做响应,比如 3 秒,规定时间内没返
回,就认为是超时。
如果我们的 HTTP 请求将超时设置的很大,比如 30 秒,在这 30 秒内只要服务器收到了扫码请
求,就立⻢返回给客⼾端网⻚。如果超时,那就立⻢发起下一次请求。
这样就减少了 HTTP 请求的个数,并且由于大部分情况下,用⼾都会在某个 30 秒的区间内做扫码
操作,所以响应也是及时的。

比如,某度云网盘就是这么干的。所以你会 发现一扫码,手机上点个确认,电脑端网⻚就秒跳
转,体验很好。

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

像这种,在用⼾不感知的情况下,服务器将数据推 送给浏览器的技术,就是所谓的服务器推送技
术,它还有个毫不沾边的英文名,comet 技术,大家听过就好。
上面提到的两种解决方案(不断轮询和⻓轮询),本质上,其实还是客⼾端主动去取 数据。
对于像扫码登录这样的简单场景还能用用。但如果是网⻚游戏呢,游戏一般会有大量的数据需要
从服务器主动推送到客⼾端。
这就得说下 WebSocket 了。
WebSocket 是什么
我们知道 TCP 连接的两端,同一时间里,双方都可以主动向对方发送数据。这就是所谓的全双
工。
而现在使用最广泛的 HTTP/1.1 ,也是基于TCP 协议的,同一时间里,客⼾端和服务器只能有一方
主动发数据,这就是所谓的半双 工。
也就是说,好好 的全双工 TCP ,被 HTTP/1.1 用成了半双 工。
为什么 ?
这是由于 HTTP 协议设计 之初,考虑的是看看 网⻚文本的场景,能做到客⼾端发起请求再由服务器
响应,就够了,根本就没考虑网⻚游戏这种,客⼾端和服务器之间都要互相主动发大量数据的场
景。
所以,为了 更好的支持这样的场景,我们需要另外一个基于TCP 的新协议。
于是新的应用层协议WebSocket 就被设计 出来了。
但其实 socket 和 WebSocket 之间,就跟雷峰和雷峰塔一样,二者接近毫无关系。

怎么建立WebSocket 连接
我们平时刷网⻚,一般都是在浏览器上刷的,一会刷刷 图文,这时候用的是 HTTP 协议,一会打
开网⻚游戏,这时候就得切换成我 们新介绍的 WebSocket 协议。
为了 兼容这些使用场景。浏览器在 TCP 三次握手建立连接之后,都统一使用 HTTP 协议先进行一
次通信。
如果此时是 普通的 HTTP 请求,那后续双方就还是老样子继续用普通 HTTP 协议进行交互 ,这
点没啥疑问。
如果这时候是想建立 WebSocket 连接,就会在 HTTP 请求里带上一些特殊的header 头,如
下:
Connection: Upgrade
Upgrade: WebSocket
Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg==\r\n这些 header 头的意思是,浏览器想升级协议(Connection: Upgrade ),并且想升级成
WebSocket 协议(Upgrade: WebSocket )。同时带上一段随机生成的 base64 码(Sec-WebSocket-Key ),发给服务器。
如果服 务器正好支持升级成 WebSocket 协议。就会走 WebSocket 握手流程,同时根据客⼾端生
成的 base64 码,用某个公开的算法变成另一段字符串,放在 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\nHTTP 状态码=200 (正常响应)的情况,大家⻅得多了。101 确实不常⻅,它其实是指协议切换。

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

就这样经历了一来一回两次 HTTP 握手,WebSocket 就建立完成了,后续双方就可以使 用
webscoket 的数据格式进行通信了。

WebSocket 抓包
我们可以用wireshark 抓个包,实际看下数据包的情况。

上面这张图,注意画了红框的第 2445 行报文,是WebSocket 的第一次握手,意思是发起了一次带
有 特殊 Header 的HTTP 请求。

上面这个图里画了红框的 4714 行报文,就是服务器在得到第一次握手后,响应的第二次握手,可
以看到这也是个 HTTP 类型的报文,返回的状态码是 101 。同时可以看到返回的报文 header 中也
带有各种 WebSocket 相关的信息,比如 Sec-WebSocket-Accept 。

上面这张图就是全貌了,从截图上的注释可以看出,WebSocket 和HTTP 一样都是基于TCP 的协议。
经历了三 次TCP 握手之后,利用 HTTP 协议升级为 WebSocket 协议。
你在网上可能会看到一种说法:"WebSocket 是基于HTTP 的新协议",其实这并不对,因为
WebSocket 只有在建立连接时才用到了HTTP ,升级完成之后就跟HTTP 没有任何 关系了。
这就好像你喜欢的女生通过你要到了你大学室 友的微信,然后他们自己就聊起来了。你能说这个
女生是通过你去跟你室友沟通的吗?不能。你跟HTTP 一样,都只是个工具人。
这就有点"借壳生蛋"的那意思。
HTTP 和WebSocket 的关系
WebSocket 的消息格式
上面提到在完成协议升级之后,两端就会用webscoket 的数据格式进行通信。
数据包在WebSocket 中被叫做帧,我们来看下它的数据格式⻓什么 样子。

这里面字段很多,但我们只需要关注下面这几个。
opcode 字段:这个是用来标志这是个什么 类型的数据帧。比如。
等于 1 ,是指text 类型( string )的数据包
等于 2 ,是二进制数据类型( []byte )的数据包等于 8 ,是关闭连接的信号
payload 字段:存放的是我们真正想要传输的数据的⻓度,单位是字节。比如你要发送的数据是
字符串 "111" ,那它的⻓度就是 3 。

另外,可以看到,我们存放** payload ⻓度的字段有好几个** ,我们既可以用最前面的 7bit , 也
可以用后面的 7+16bit 或 7+64bit 。
那么问题就来了。
我们知道,在数据层面,大家都是 01 二进制流。我怎么知道什么 情况下应该读 7 bit ,什么 情况下
应该读7+16bit 呢?
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 去传输数据,会有粘包的"问题"。为了 解
决这个问题,上层协议一般会用消息头+消息体的格式去重新包装要发的数据。
而消息头里一般含有消息体的⻓度,通过这 个⻓度可以去截取真正的消息体。
HTTP 协议和大部分 RPC 协议,以及我们今天介绍的WebSocket 协议,都是这样设计 的。

WebSocket 的使用场景
WebSocket 完美继承了 TCP 协议的全双工能力,并且还贴心的提供了解决粘包的方案。
它适用于需要服务器和客⼾端(浏览器) 频繁交互 的大部分场景,比如网⻚/小程序游戏,网⻚聊
天室,以及一些类似⻜书这样的网⻚协同办公软件。
回到文章开头的问题,在使用 WebSocket 协议的网⻚游戏里,怪物移动以及攻击玩家的行为是服
务器逻辑产生的,对玩家产生的伤害等数据,都需要由服务器主动发送给客⼾端,客⼾端获得数
据后展示对应的效果。

总结
TCP 协议本身是全双工的,但我们最常用的 HTTP/1.1 ,虽然是基于 TCP 的协议,但它是半双 工
的,对于大部分需要服务器主动推送数据到客⼾端的场景,都不太友好,因此我们需要使用支
持全双工的 WebSocket 协议。
在 HTTP/1.1 里,只要客⼾端不问,服务端就不答。基于这样的特点,对于登录⻚面这样的简单
场景,可以使 用定时轮询或者⻓轮询的方式实现服务器推送(comet) 的效果。对于客⼾端和服务端之间需要频繁交互 的复杂场景,比如网⻚游戏,都可以考虑使用
WebSocket 协议。
WebSocket 和 socket 几乎没有任何 关系,只是叫法相似。
正因为各个浏览器都支持 HTTP 协 议,所以 WebSocket 会先利用HTTP 协议加上一些特殊的
header 头进行握手升级操作,升级成功后就跟 HTTP 没有任何 关系了,之后就用 WebSocket 的
数据格式进行收发数据。
