前端如何实现长连接之使用WebSocket长连接

循序渐进、一点点学习了解:

前端实现长连接的主要方法有:使用 WebSocket、使用 Server-Sent Events (SSE)、使用长轮询。在这些方法中,WebSocket 是最常用和最强大的工具,因为它提供了双向通信的能力,可以在客户端和服务器之间保持一个持续的连接,从而实现实时数据传输。

WebSocket 是什么?和 HTTP 是什么区别?长轮询是什么?服务器推是什么?

平时我们打开网页,比如:购物网站某宝,都是点一下列表商品,跳转一下网页就到了商品详情。从 HTTP 协议的角度来看,就是点一下网页上的某个按钮, 前端发一次 HTTP 请求 ,网站返回一次 HTTP 响应 。这种由客户端 主动请求服务器响应 的方式也满足大部分网页的功能场景。但有没有发现,这种情况下,服务器从来就不会主动给客户端发一次消息。就像你喜欢的女生从来不会主动发消息找你一样

但我们假设有那么个场景,你打开浏览器,就像往常一样刷网页 ,这时候右下角 突然弹出一个小广告 ,提示你一个人在家偷偷才能玩哦。求知好学勤奋,这些刻在你 DNA 里的东西都动起来了。你点开后发现长相平平无奇的古某提示你道士九条狗全服横着走。影帝某辉老师跟你说系兄弟就来砍我。来都来了,你就选了个角色进到了游戏界面里。这时候上来就是一个小怪,从远处走来,然后疯狂喷火攻击你。你全程没点任何一次鼠标,服务器自动 将怪物的移动数据和攻击数据源源不断发给你了。这,,,也太暖心了吧!感动之余问题就来了,像这种看起来服务器主动发消息给客户端的场景,是怎么做到的?

在真正回答这个问题之前,我们先来聊下一些相关的知识背景。其实问题的痛点在于,怎么样才能在用户不做任何操作的情况下,网页能收到消息并发生变更。最常见的解决方案是网页的前端代码里 不断 定时发 HTTP 请求到服务器 ,服务器 收到请求后给 客户端 响应消息 。这其实是一种 伪服务器推 的形式,它其实并不是服务器主动发消息到客户端,而是客户端自己 不断 偷偷请求 服务器,只是 用户无感知而已。

用这种方式的场景也有很多,最常见的就是 扫码登录 ,比如某信 的平台,登录页面二维码出现之后,前端网页根本不知道用户扫没扫,于是不断去向后端服务器询问,看有没有人扫过这个码。而且是以大概 1~2 秒的间隔去不断发出请求 ,这样可以保证用户在扫码后能在 1~2 秒内得到及时的反馈,不至于等太久。这就是 HTTP 定时轮询 。但这样会有两个比较明显的问题,第一个是当你打开 F12 页面时,你会发现 满屏HTTP 请求 。虽然很小,但这其实也 消耗带宽 ,同时也会增加下游服务器负担。第二个问题是,最快情况下,用户在扫码后需要等个 1 到 2 秒,正好才触发下一次 HTTP 请求,然后才跳转页面,用户会感到明显的卡顿。那么问题又来了,有没有更好的解决方案?

有,而且实现起来成本还非常低,它就是 长轮询。我们知道 HTTP 请求发出后,一般会给服务器留一定的时间做响应,比如 3 秒。规定时间内没返回,就认为是超时。如果我们的 HTTP 请求将 超时 设置的 很 ,比如 30 秒 ,在这 30 秒内,只要服务器收到了扫码请求,就立马返回给客户端网页。如果超时,那就立马发起下一次请求。这样就减少 了 HTTP 请求 的个数,并且由于大部分情况下,用户都会在某个 30 秒的区间内做扫码操作,所以响应也是及时的。比如某度云网盘就是这么干的。所以你会发现一扫码,手机上点个确认,电脑端网页就秒跳转,体验很好 ,真 · 一举两得。像这种发起一个请求,在较长时间内等待服务器响应的机制,就是所谓的 长轮询机制 。我们常用的消息队列 RocketMQ 中,消费者去取数据时也用到了这种方式。像这种在用户不感知的情况下,服务器将数据推送给浏览器的技术,就是所谓的 服务器推送 技术。它还有个毫不沾边的英文名, Comet 技术 ,大家听过就好。上面提到的两种解决方案,本质上其实还是 客户端主动去取数据。对于像扫码登录这样的简单场景还能用用。但如果是网页游戏呢?游戏一般会有大量的数据需要从服务器主动推送到客户端,这就得说下 WebSocket 了。


我们知道 TCP 连接的两端同一时间里双方都可以 主动向对方发送数据,这就是所谓的 全双工,而现在使用最广泛的 HTTP 1.1 也是基于 TCP 协议的同一时间里客户端和服务器只能有一方 主动发数据这就是所谓的 半双工 ,也就是说,好好的全双工 TCP 被 HTTP 弄成了半双工,为什么?这是由于 HTTP 协议设计之初考虑的是看看网页文本的场景能做到客户端 发起请求 再由服务器响应 就够了,根本就没考虑网页游戏这种客户端和服务器之间都要互相主动发大量数据的场景,所以为了更好的支持这样的场景我们需要另外一个基于 TCP新协议,于是新的应用层协议 WebSocket 就被设计出来了,大家别被这个名字给带偏了,虽然名字带了个 Socket ,但其实 Socket 和 WebSocket 之间就跟雷锋和雷峰塔一样,二者接近毫无关系,那么怎么建立 WebSocket 连接呢?

我们平时刷网页,一般都是在浏览器上刷的,一会刷刷图文 ,这时候用的是 HTTP 协议 ,一会打开网页页游戏 ,这时候就得切换成我们新介绍的 WebSocket 协议 ,一会还得看个视频 ,为了兼容这些使用场景,浏览器在 TCP 三次握手建立连接之后,都统一使用 HTTP 协议先进行一次通信 ,如果此时是 普通的 HTTP 请求,那后续双方就还是老样子继续用普通 HTTP 协议进行交互 ,这点没啥疑问,如果这时候是想建立 WebSocket 连接,就会在 HTTP 请求 里带上一些特殊的 header 头 ,其中 connection: upgrade ,表明浏览器想升级协议 ,从 upgrade: websocket 可以看出,客户端想升级成 websocket 协议 ,同时带上一段随机生成的 Base64 码,也就是 sec-websocket- key 发给服务器,如果服务器正好支持升级成 websocket 协议,就会走 WebSocket 握手流程,同时根据客户端生成的 Base 64 码,用某个公开的算法变成另一段字符串,放在 HTTP 响应的 sec-websocket-accept 头里,同时带上 101 状态码 发回给浏览器, 101 确实不常见,它其实是指协议切换,之后浏览器也用同样的公开算法将 Base64 码转成另一段字符串,如果这段字符串跟服务器传回来的字符串一致,那验证通过。

WebSocket 和 HTTP 一样都是基于 TCP 的协议,经历了三次 TCP 握手之后,利用 HTTP 协议升级为 WebSocket 协议,后续双方就使用 WebSocket 的数据格式进行通信,数据包在 WebSocket 中被叫做帧。

我们来看一下它的数据格式长什么样子,这里面字段很多,但我们只需要关注下面这几个, Opcode 字段 ,这个是用来标志这是个什么类型数据帧 ,比如等于 1 时是指 Text 类型,也就是 String 类型数据包 ,等于 2 是二进制数据类型 的数据包,等于 8 是关闭连接的信号。

Payload 字段 存放的是我们真正想要传输的数据的长度单位是字节,比如你要发送的数据是字符串 111 ,那它的长度就是三,另外可以看到我们存放 Payload 长度的字段有好几个,我们既可以用最前面的 7bit ,也可以用后面的 7+16bit 或 7+64bit, 那么问题就来了,在数据层面,大家都是 01 二进制流,我怎么知道什么情况下应该读 7bit ,什么情况下应该读 7+16bit 呢, WebSocket 会用最开始的 7bit 做标志位 ,不管接下来的数据有多大,都先读最先的 7个bit, 根据它的取值决定,还要不要再读个 16bit 或 64bit ,如果最开始的 7bit 的值是 0-125,那么它就表示了 Payload 全部长度,只读最开始的 7个bit 就完事了,如果最开始的 7bit 的值是126,也就是 16进制的 0X7E,那它表示 Payload 的长度范围,在 126 - 65535 之间,接下来还需要再读 16bit,这 16bit 包含 Payload 的真实长度,如果最开始的 7bit 的值是127,也就是 16进制的 0X7F,那它表示,Payload 的长度范围大于等于 65536,接下来还需要再读 64bit,这 64bit 会包含 Payload 的长度,这能放 2 的 64次方 Byte 的数据,换算一下好多个 TB,肯定够用,剩下的就是 Payload ,data 字段,这里放的是真正要传输的数据,在知道了上面的 Payload 长度后,就可以根据这个值去截取对应的数据。

大家有没有发现一个小细节,WebSocket 的数据格式也是消息头 + 消息体的格式 ,也就是 payload + payload data 的形式,之前写的,《既然有 HTTP 协议,为什么还要有 RPC》提到过,TCP 协议本身就是全双工 ,但直接使用纯裸 TCP 传输数据会有粘包的问题,为了解决这个问题,上层协议一般会用消息头 + 消息体的格式 ,去重新包装要发的数据,消息头里一般含有消息体的长度,通过这个长度可以去截取真正的消息体,HTTP 协议和大部分 RPC 协议以及我们今天介绍的 WebSocket 协议,都是这样设计的,你在网上可能会看到一种说法,"WebSocket 是基于 HTTP 的新协议",其实这并不对,因为 WebSocket只有在建立连接时,才用到了 HTTP,升级完成之后,就跟 HTTP 没有任何关系了,这就好像你喜欢的女生通过你要到了你大学室友的微信,然后他们自己就聊起来了 ,你能说这个女生是基于你去跟你室友沟通的吗?不能,你跟 HTTP 一样都只是个工具人 ,这就有点借壳生蛋 的那意思,WebSocket 完美继承了 TCP 协议的全双工能力 ,并且还贴心的提供了解决粘包的方案,它适用于需要 服务器 和 客户端 频繁交互 的 大部分场景,比如网页小程序游戏网页聊天室 以及一些类似飞书这样的网页,协同办公软件。

回到开头的问题,在使用 WebSocket 协议的网页游戏 里,怪物移动以及攻击玩家的行为,是服务器逻辑产生 的,对玩家产生的伤害等数据,都需要由服务器主动发送给客户端,客户端获得数据后展示对应效果,好了,到这里 WebSocket 的知识点就讲完了。

相关推荐
We་ct2 小时前
LeetCode 201. 数字范围按位与:位运算高效解题指南
开发语言·前端·javascript·算法·leetcode·typescript
Patrick_Wilson2 小时前
你的 MR 超过 500 行了吗?——大型代码合并请求拆分实战指南
前端·代码规范·前端工程化
神三元2 小时前
大模型工具调用输出的 JSON,凭什么能保证不出错?
前端·ai编程
得物技术2 小时前
基于 Cursor Agent 的流水线 AI CR 实践|得物技术
前端·程序员·全栈
188号安全攻城狮3 小时前
【前端安全】Trusted Types 全维度技术指南:CSP 原生 DOM XSS 防御终极方案
前端·安全·网络安全·xss
墨渊君3 小时前
从 0 到 1:用 Node 打通 OpenClaw WebSocket 通信全流程
前端·openai·agent
Novlan13 小时前
一个油猴脚本,解决掘金编辑器「转存失败」的烦恼
前端
前端老石人3 小时前
HTML 入门指南:从规范视角建立正确知识体系
开发语言·前端·html