目录
- 1 为什么需要 WebSocket?
- 2 WebSocket
- 2.1 采用 TCP 全双工
- 2.2 建立 WebSocket 连接
- 2.3 WebSocket 帧
- 3 WebSocket 解决的问题
- 3.1 HTTP 存在的问题
- 3.2 Ajax 轮询存在的问题
- 3.3 长轮询存在的问题
- 3.4 WebSocket 的改进
参考资料:
- 为什么有 http 还要有 websocket - BiliBili
- WebSocket - CSDN
1 为什么需要 WebSocket?
在使用 HTTP 协议时,用户点击网页上的按钮,客户端发送一次请求,服务器返回一次响应,如下图所示。不难发现,服务器从来都不会主动向客户端发送一次请求。
因此在 HTTP 协议中,客户端是主动方,服务器是被动方。
考虑网页游戏的场景,通常情况下玩家不需要点击鼠标,怪物就能够源源不断地刷新出来。也就是说,在用户不做任何操作的情况下,客户端能够收到消息并发生变更。那么这种看起来像是服务器主动发送消息给客户端的场景是如何实现的呢?
何谓 “看起来像是”?答:在定时轮询和长轮询中,仍然是客户端主动发送请求,只是用户感知不到,误以为是服务器在主动发送数据。
最常见的做法是「HTTP 定时轮询」,客户端定时自动发送 HTTP 请求到服务器,服务器收到请求后对客户端进行响应,如下图所示。这是一种「伪服务器推」的形式,它其实并不是服务器主动发送消息到客户端,而是客户端不断请求服务器,只是用户无感知而已。
使用该方式的场景很多,最常见的就是扫码登录。比如微信的登录平台,前端网页不知道用户是否完成扫码,于是不断向后端服务器询问,如下图所示。一般请求间隔是在 1 到 2 秒之间,保证用户在扫码后的 1 到 2 秒中能够得到反馈。
以上便是「HTTP 定时轮询」,它的缺点在于:
- 每次请求消耗带宽,增加了下游服务器的负担;
- 最快情况下,用户也需要等待 1 到 2 秒才能触发下一次 HTTP 请求,完成页面的跳转。
说明:由于服务器是被动方,即使它知道用户扫码成功了也没办法主动告诉客户端,需要等待客户端的下一次请求,因此用户需要等待 1 到 2 秒才能完成页面的跳转。
更好的方案是「HTTP 长轮询」,如下图 (b) 所示。HTTP 请求发送后一般会留一定的时间给服务器做响应,比如 3 秒,规定时间内没有返回就是超时。如果增大超时的时间,比如 30 秒,在这 30 秒内,只要服务器收到了用户的扫码请求,就可以立马返回给客户端。如果超时,那么客户端就立马发起下一次请求。
说明:HTTP 协议的通信模式是一个请求搭配一个响应,即一问一答的模式。在定时轮询中,即使服务器知道了答案,但只要客户端没问,它就不能答。而在长轮询中,客户端提问后服务器并不急着回答,以避免浪费本次提问,从而保证了一旦服务器知道了答案就能立即进行回答。
这样就减少了 HTTP 请求的个数,并且响应也是及时的。采用该方案的有百度网盘,如下图所示:
2 WebSocket
上述两种解决方案,本质上还是客户端主动取数据,适用于扫码登录这种简单的应用场景。而网页游戏中有大量的数据需要由服务器主动推送到客户端,这就需要使用 WebSocket 技术了。
2.1 采用 TCP 全双工
维基百科
- 半双工:半双工的系统允许二台设备之间的双向资料传输,但不能同时进行。因此同一时间只允许一设备发送资料,若另一设备要发送资料,需等原来发送资料的设备发送完成后再处理。
- 全双工:全双工的系统允许二台设备间同时进行双向资料传输。
TCP 协议支持全双工
虽然目前使用最广泛的 HTTP1.1 基于 TCP 协议,但是它规定同一时间内客户端和服务器只能有一方主动发送数据。
这是因为 HTTP 在设计之初,考虑的是在网页中浏览文本的场景,能做到客户端发起请求、服务器进行响应即可。它并未考虑网页游戏这种,客户端和服务器之间需要相互主动发送大量数据的场景。为了更好地支持这样的场景,我们需要一个基于 TCP 的新协议 —— WebSocket 协议。
注意:Socket 和 WebSocket 的区别就像是雷锋和雷峰塔的区别。
2.2 建立 WebSocket 连接
在浏览网页时,我们一会儿使用 HTTP 协议看图文,一会儿使用 WebSocket 协议打游戏,一会儿刷视频。为了兼容这些应用场景,要求客户端和服务器在 TCP 三次握手建立起连接后,都统一使用 HTTP 请求先进行一次通信。如下图所示:
如果该请求是普通的 HTTP 请求,那么双方继续使用 HTTP 协议进行交互。如果该请求是建立 WebSocket 的请求,那么请求里会带上一些特殊的头部信息:
Connection: Upgrade # 客户端想升级协议
Upgrade: WebSocket # 客户端想升级成WebSocket协议
Sec-WebSocket-Key: T2a6wZlAwhgQNqruZ2YUyg # 用于验证的BASE64码
服务器使用公开算法将客户端的 BASE64 码变成一段字符串,放在 HTTP 响应里:
HTTP/1.1 101 Switching Protocols # 101协议切换状态码
Sec-WebSocket-Accept: iBJKv/ALlW2DobfoA4dmr3JHBCY= # 字符串
Upgrade: WebSocket
Connection: Upgrade
客户端也使用相同的公开算法将 BASE64 码变成一段字符串,如果和服务器传回来的字符串一致,那么验证通过。
WebSocket 协议(ws 协议)和 HTTP 协议都是基于 TCP 协议。在完成 TCP 三次握手之后,可以利用 HTTP 请求将 HTTP 协议升级为 ws 协议,后续双方使用 ws 协议的数据格式进行通信。如下图所示:
2.3 WebSocket 帧
在 ws 协议中,数据包被称为帧:
- opcode 用于指明帧的数据类型,=1 是指 text 类型,=2 是指二进制类型
- payload 用于指明所传输的数据的长度,单位是字节
3 WebSocket 解决的问题
这一小节相当于是总结了前文各种解决方案的优缺点。
3.1 HTTP 存在的问题
- HTTP 协议是一种无状态协议,其特性决定了在每次会话结束后,服务器端无法识别下一次发起请求的客户端身份。因此,为了确保通信的准确性,每次通信都必须重新确认对方身份,对实时通讯造成了障碍。
- HTTP 协议的通信遵循一次请求对应一次响应的模式。每次请求和响应都携带了大量的头部信息,这对于实时通讯而言,解析这些头部信息无疑增加了处理时间,从而降低了通信效率。
- HTTP 协议的通信机制是客户端主动发起请求,服务器被动响应。这种模式限制了服务器端的主动性,即服务器无法在没有客户端请求的情况下主动发送信息,从而无法实现实时通讯中的主动推送功能。
3.2 Ajax 轮询存在的问题
Ajax 轮询技术是指,客户端定期发起请求,以查询是否有新消息。若有新消息,则立即返回;若无,则等待预设的时间间隔后再次发起查询。该技术弥补了 HTTP 协议在实时更新方面的不足。
以一个具体场景为例:张三正在等待快递,由于他迫不及待,因此每隔 10 分钟就打电话给快递站询问快递是否到达。快递站无法主动通知张三,只有等到张三的电话才能告知他快递已到。
从上述例子中,我们可以看出两个问题:
- 假设张三的通话间隔为 10 分钟,在他收到快递前最后一次通话被告知尚未到达后,如果快递随即入库,那么张三在下一次通话时才能得知快递已到。这种通讯方式并不能算作实时通讯,因为可能存在 10 分钟的时间差。
- 假设张三所在的小区每天都有大量的快递需要接收,且每个人都采取主动致电的方式,那么快递站的电话占线也成为问题。
综上所述,Ajax 轮询技术存在的问题主要包括:
- 推送延迟:即使数据变更,服务器也只能在客户端发来请求时返回响应。
- 服务器压力:频繁的轮询会对服务器造成较大压力。
- 难以平衡推送延迟与服务器压力:减小轮询间隔虽能降低延迟,但会增加压力;增大轮询间隔虽能减轻压力,但会提高延迟。
3.3 长轮询存在的问题
针对上述 HTTP 协议在实时通讯方面的局限性,出现了一种解决方案 —— 长轮询技术。
长轮询技术是在 HTTP 协议的基础上,由客户端发起的一种特殊请求。在该机制中,如果服务器的数据没有发生变化,服务器将暂时挂起客户端的请求,直至数据更新或达到设定的超时时间才返回响应。如果超时,那么客户端会在收到响应后立即发起下一次长轮询请求。该技术克服了 HTTP 协议无法实现实时更新的缺陷,即一旦数据更新,服务器便迅速处理请求并返回响应。
在长轮询机制中,张三主动致电快递站并保持通话直至快递到达。如下图所示:
总体而言,长轮询技术存在以下优点:
- 没有推送延迟:服务器数据变更后,长轮询结束,能够迅速向客户端返回响应。
- 服务器压力小:长轮询的间隔通常较长,如 30 秒或 60 秒,且服务器在挂起连接期间并不会消耗过多的资源。
个人理解:虽然长轮询克服了定时轮询的诸多缺点,但是长轮询本质上还是需要客户端主动去发起请求,即需要张三亲自打电话去询问快递站,快递站是不会主动通知张三去取快递的。而 WebSocket 允许快递站通知张三,以帮助张三及时取到快递,因此它的实时性更好。
3.4 WebSocket 的改进
一旦 WebSocket 连接得以建立,后续的数据传输均采用帧序列的形式。在客户端主动断开 WebSocket 连接或服务器终止连接之前,双方无需重新发起连接请求。在处理大量并发连接及客户端与服务器之间交互频繁的场景下,此举显著降低了网络带宽资源的消耗,并展现出卓越的性能优势。客户端与服务器之间的消息发送与接收均在同一持久连接上进行,实现了真正的 “长连接”,其实时性优势尤为突出。