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

张开发
2026/4/6 22:18:19 15 分钟阅读

分享文章

前端如何实现长连接之使用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 协议同时带上一段随机生成的 base 64 码也就是 sec-websocket- key 发给服务器如果服务器正好支持升级成 websocket 协议就会走 WebSocket 握手流程同时根据客户端生成的 Base 64 码用某个公开的算法变成另一段字符串放在 HTTP 响应的 sec-ws-accept 头里同时带上 101 状态码发回给浏览器 101 确实不常见它其实是指协议切换之后浏览器也用同样的公开算法将 Base64 码转成另一段字符串如果这段字符串跟服务器传回来的字符串一致那验证通过 WebSocket 和 HTTP 一样都是基于 TCP 的协议经历了三次 TCP 握手之后利用 HTTP 协议升级为 WebSocket 协议后续双方就使用 WebSocket 的数据格式进行通信数据包在 WebSocket 中被叫做帧我们来看一下它的数据格式长什么样子这里面字段很多但我们只需要关注下面这几个 Opcode 字段这个是用来标志这是个什么类型的数据帧比如等于 1 时是指 Text 类型也就是 String 类型的数据包等于 2 是二进制数据类型的数据包等于 8 是关闭连接的信号 Payload 字段存放的是我们真正想要传输的数据的长度单位是字节比如你要发送的数据是字符串 111 那它的长度就是三另外可以看到我们存放 Payload 长度的字段有好几个我们既可以用最前面的 7bit 也可以用后面的 716bit 或 764bit 那么问题就来了在数据层面大家都是 01 二进制流我怎么知道什么情况下应该读 7bit 什么情况下应该读 716bit 呢 WebSocket 会用最开始的 7bit 做标志位不管接下来的数据有多大都先读最先的 7个bit 根据它的取值决定还要不要再读个 16bit 或 64bit 如果最开始的 7bit 的值是 0-125那么它就表示了 Payload 全部长度只读最开始的 7个bit 就完事了如果最开始的 7倍 的值是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的知识点就讲完了。

更多文章