背景

平时我们打开网页,比如购物网站某宝。都是点一下「列表商品」,跳转一下网页就到了「商品详情」。

从 HTTP 协议的角度来看,就是点一下网页上的某个按钮,前端发一次 HTTP请求,网站返回一次 HTTP 响应。这种由客户端主动请求,服务器响应的方式也满足大部分网页的功能场景。这种情况下,服务器从来就「不会主动」给客户端发一次消息。

但是当你打游戏的时候,服务器就自动将怪物的移动数据和攻击数据源源不断发给你。像这种看起来服务器主动发消息给客户端的场景是怎么做到的?这就得说下 WebSocket 了。

一、WebSocket是什么

我们知道 TCP 连接的两端,同一时间里双方都可以主动向对方发送数据。这就是所谓的全双工

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

为什么?

这是由于 HTTP 协议设计之初,考虑的是看看网页文本的场景,能做到客户端发起请求再由服务器响应,就够了,根本就没考虑网页游戏这种,客户端和服务器之间都要互相主动发大量数据的场景。所以,为了更好的支持这样的场景,我们需要另外一个基于TCP的新协议。于是新的应用层协议WebSocket就被设计出来了

图片

1.1 怎么建立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\n

HTTP 状态码=200(正常响应)的情况,大家见得多了。101 确实不常见,它其实是指协议切换

图片

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

图片

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

图片

1.2 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没有任何关系了

1.2 WebSocket的使用场景

WebSocket完美继承了 TCP 协议的全双工能力,并且还贴心的提供了解决粘包的方案。

它适用于需要服务器和客户端(浏览器)频繁交互的大部分场景,比如网页/小程序游戏,网页聊天室,以及一些类似飞书这样的网页协同办公软件。

二、总结

  1. TCP 协议本身是全双工的,但我们最常用的 HTTP/1.1,虽然是基于 TCP 的协议,但它是半双工的,对于大部分需要服务器主动推送数据到客户端的场景,都不太友好,因此我们需要使用支持全双工的 WebSocket 协议。

  2. 在 HTTP/1.1 里,只要客户端不问,服务端就不答。基于这样的特点,对于登录页面这样的简单场景,可以使用定时轮询或者长轮询的方式实现服务器推送(comet)的效果。

  3. 对于客户端和服务端之间需要频繁交互的复杂场景,比如网页游戏,都可以考虑使用 WebSocket 协议。

  4. WebSocket 和 socket 几乎没有任何关系,只是叫法相似。

  5. 正因为各个浏览器都支持 HTTP协 议,所以 WebSocket 会先利用HTTP协议加上一些特殊的 header 头进行握手升级操作,升级成功后就跟 HTTP 没有任何关系了,之后就用 WebSocket 的数据格式进行收发数据。

本文参考

小林coding之HTTP篇