前端数据实时更新的巧妙实现,大揭秘!

06-01 1208阅读

数据实时更新的多种实现方式

一、前言

如今,Web 应用愈发复杂,用户对实时交互体验的要求也越来越高,比如:社媒的即时通讯、大屏的数据更新、实时消息的提醒等,这些都表明实时交互已成高品质应用的必备特性。而作为开发者,我们常面对复杂的开发环境,要应对即时通讯与数据实时更新的问题。那么,该如何精准高效实现这些功能呢?我们将共同探讨下,轮询、Web Socket 、SSE(Server-Sent Events) 三种解决方案,最终根据当下场景选出最优方案,打造更为出色的产品。

二、方案一:轮询(Polling)

1. 短轮询

实现短轮询,我们可以采用定时器的方式来实现,让客户端每隔较短固定时间就向服务端发起请求,无论服务器有无新消息,都会正常给予回应。

短轮询的优劣势一目了然。优势在于,容易理解、实现过程简便,同时兼容性又很好,在几乎所有支持 HTTP 协议的浏览器及服务器环境都能很好的运行短轮询。不过,缺点也非常显而易见。当按照很短的固定时间间隔去频繁请求数据,如果此时的数据并未更新,这些请求就成了无效请求,但是每一个无效的请求都得完成 HTTP 建立连接的一系列流程,像三次握手 、 四次挥手 ,这无疑造成了不必要的资源浪费。同时也是由于按固定间隔请求,数据更新也可能会存在延迟的现象,在要求实时性的场景下就不满足了。

前端数据实时更新的巧妙实现,大揭秘!

2. 长轮询

相对于短轮询,长轮询的实现过程会复杂一些。首先,同样是由客户端向服务端发送HTTP请求后,不过与短轮询的差别在于,服务器接到请求后并不即刻返回响应,而是选择将该请求暂时挂起,静静等待数据更新,或是直至达到特定的超时时间(这个超时时间通常比短轮询间隔长)。在等待期间,如果有数据更新了,就会立即将新数据返回给客户端,若一直未等到数据更新,直到达到超时时间,服务器才返回一个空响应或者告知客户端无新数据。客户端收到响应后,无论是否有新数据,都会立即再次发起新的长轮询请求,如此循环往复。

这种长轮询方式相较短轮询存在一定优势,首先减少了无效请求的次数,因为只有在数据更新的时候,服务器才会响应,实时性得到了增强,也同时在一定程度上节省了网络资源。长轮询虽然减少了无效请求,但是长时间挂起的请求仍会占用服务器的内存、线程等关键资源。若同时存在大量长轮询请求,且长时间处于等待数据更新状态,服务器资源可能会被大量消耗,同样可能引发性能问题。

前端数据实时更新的巧妙实现,大揭秘!

由此可见, 无论是运用长轮询还是短轮询策略,在对实时性有着严格要求的场景之下这都不是一个非常好的方案。一方面,频繁的无效请求以及长时间挂起的连接,极易造成服务器资源的浪费。另一方面,由于轮询时间间隔的固定性,一旦设置的不合理,就可能错失数据更新的最佳时机,无法及时将关键信息推送给客户端,导致实时交互出现延迟。

三、方案二:Web Socket

Web Socket 是一种基于单个 TCP 连接的协议,拥有实现 全双工 通信的能力。它让客户端和服务端可以双向、实时传输数据,摆脱了传统 HTTP 请求那种请求 - 响应模式的限制。有了 Web Socket,服务器能随时主动给客户端推送消息,客户端也能马上向服务器发数据,就如同构建起了一条实时双向通道。

前端数据实时更新的巧妙实现,大揭秘!

1、 Web Socket通信原理

1、 握手阶段
  • 初始请求阶段

    • 首先,我们可以从下图实现效果中看出来,起始于客户端向服务端发送了一个HTTP请求,但这个请求和常规的HTTP请求又不太一致,他包含了两个特殊的请求头, Upgrade: websocket  和  Connection: Upgrade。

    • Upgrade: websocket 像是一个信号,它的作用就是用来告知服务器,客户端希望将当前的通信协议从 HTTP升级为Web Socket协议。

    • Connection: Upgrade 是与Upgrade: websocket 进行配合使用的,它的作用是告诉服务器,不要把这个请求当作普通的 HTTP 请求来处理,而是要关注 “Upgrade” 请求头中的内容,按照要求进行协议的升级。

  • 服务器响应阶段

    • 服务器收到客户端的升级请求后,如果支持 Web Socket 协议,就会返回一个响应来完成握手过程。响应状态码通常是 101,表示 协议切换。响应头也会包含和客户端请求对应的 Upgrade 和 Connection 字段,确认连接升级。Sec - WebSocket - Accept ,它是根据客户端请求中的 Sec - WebSocket - Key 生成的,用于安全验证。通过这个握手过程,双方就建立了一个稳定的 Web Socket 连接这样,这个连接是基于 TCP的,为后续的双向通信做好了准备。

    2、 数据传输阶段
    • 我们从下图中可以看出,Web socket全双工通道的特点,客户端可以向服务端主动发送消息,服务端也可以推送数据到服务端。具体的数据格式,我们可以通过抓包软件来进行抓包看下。

      3、 连接维护和关闭阶段
      • 在整个通信过程中,Web Socket 协议要求客户端和服务器都要维护连接的状态。连接状态包括连接是否打开、正在关闭或者已经关闭等。客户端和服务器通过心跳机制(发送周期性的小数据包来检测对方是否还在线)或者其他自定义的连接检测方法来确保连接的稳定性。

      • 当需要关闭 WebSocket 连接时,无论是客户端还是服务器都可以发起关闭请求。关闭请求也是通过发送一个特定的帧(关闭帧)来实现的。对方在收到关闭帧后,会进行一些必要的清理工作,如释放资源等,然后关闭连接。

        2、示例

        • 模拟客户端代码

            
            发送消息
            
              

          收到的回复记录:

              
                  {{ msg }}
        •     
             const app = new Vue({     el: "#app",     data() {       return {         message: "",         receivedMessages: [], // 新增数组用于存储所有收到的消息         socket: null,       };     },     mounted() {       // 创建WebSocket连接,这里的地址要和后端服务器监听的地址对应,这里假设后端在本地3000端口       this.socket = new WebSocket("ws://localhost:3000");       // 连接成功时触发的事件       this.socket.addEventListener("open", () => {         console.log("已连接到WebSocket服务器");       });       // 接收服务器发送消息的事件       this.socket.addEventListener("message", (event) => {         const receivedMsg = event.data;         this.receivedMessages.push(receivedMsg); // 将收到的消息添加到数组中       });       // 连接关闭时触发的事件       this.socket.addEventListener("close", () => {         console.log("与WebSocket服务器的连接已关闭");       });     },     methods: {       sendMessage() {         if (this.socket.readyState === WebSocket.OPEN) {           this.socket.send(this.message);           this.message = "";         } else {           console.log("WebSocket连接未就绪,无法发送消息");         }       },     },   });
        • 模拟服务端发送请求

          注意:下方代码中,定时器仅用来模拟,在实际业务中应该替换为业务代码

          const WebSocket = require('ws');
          // 创建WebSocket服务器实例,监听在3000端口,你可以根据需求修改端口号
          const wss = new WebSocket.Server({ port: 3000 });
          // 用于存储已连接的客户端WebSocket实例,方便后续向所有客户端发送消息等操作
          const clients = [];
          // 当有客户端连接时触发的事件
          wss.on('connection', (ws) => {
            console.log('客户端已连接');
            clients.push(ws);
            // 接收客户端发送的消息
            ws.on('message', (message) => {
              console.log(`收到客户端消息: ${message}`);
              // 这里简单地将收到的消息加上一个后缀后再发回客户端
              const responseMessage = `你发送的消息是:${message}`;
              ws.send(responseMessage);
            });
            // 当客户端关闭连接时触发的事件
            ws.on('close', () => {
              console.log('客户端已断开连接');
              const index = clients.indexOf(ws);
              if (index > -1) {
                clients.splice(index, 1);
              }
            });
          });
          // 【注意: 模拟一个定时任务,每隔1秒向所有已连接的客户端发送一条消息,你可以编写自己的业务代码】
          setInterval(() => {
            const messageToSend = '这是服务端主动发送的消息,当前时间:' + new Date().toLocaleString();
            clients.forEach((client) => {
              if (client.readyState === WebSocket.OPEN) {
                client.send(messageToSend);
              }
            });
          }, 1000);
          console.log('WebSocket服务器已启动,正在监听3000端口...');
          
          • 实现效果

            前端数据实时更新的巧妙实现,大揭秘!

            前端数据实时更新的巧妙实现,大揭秘!

            3、 特点

            相较于传统的 HTTP 请求,Web Socket 在实时性方面展现出了卓越优势,它打破了以往被动等待响应的模式,服务器得以实时且主动地将数据推送至客户端。在面对高实时性的场景下,大大的提升了用户体验。并且只需建立一次 TCP 连接,后续数据传输高效便捷,既减少了网络带宽的无谓占用,又减轻了服务器频繁处理连接请求的负担。然而,当应用场景出现变化,假设客户端仅仅是获取服务端推送的消息,自身并无向服务端发送信息的需求,此时,我们可以考虑下 SSE(Server-Sent Events)这个方案。

            四、方案三:SSE

            SSE(Server-Sent Events) 同样具备了服务端主动向客户端推送数据的能力,而无需客户端不断地发起请求。与Web Socket不同的是,SSE 是基于 HTTP 协议的,使用的是单向通信, 而Web Socket是基于TCP协议的,使用的话双向通信。

            前端数据实时更新的巧妙实现,大揭秘!

            1、SSE通信原理

            1、 客户端发起请求
            • 客户端需要发送一个特定的请求告知服务器准备接收事件。在传统的 HTTP 请求 - 响应模式中,一次请求完成后连接通常会关闭,但 SSE 通过在服务器端和客户端设置特定的头部信息,让连接持续开启。例如:设置了Content - Type头部为text/event - stream,并设置Cache - Control为no - cache以及Connection为keep - alive,这样告知客户端,这是一个 SSE 连接,数据会持续推送,并且不需要缓存数据。客户端只需向服务器发送一个普通的 HTTP 请求,指向服务器端提供 SSE 服务的特定端点就行,例如/events,就可以顺利开启SSE连接。

              2、 服务器响应请求
              • 服务器收到客户端请求后,会维持一个长连接,并且定期向客户端发送事件数据。每个事件都是通过特定的格式(例如 data:\n\n)发送给客户端的。

                3、 客户端接收数据
                • 客户端通过 JavaScript 的 EventSource 对象接收从服务器推送过来的数据。这些数据可以是普通文本,也可以是 JSON 格式的对象,取决于服务器如何发送。

                  4、 事件流的关闭
                  • 一旦不再需要推送数据,比如客户端主动关闭连接,又或是中途出现错误,导致无法继续推送时,服务器便会果断关闭连接。

                    2、示例

                    • 服务端,使用express框架为例

                      注意:下方代码中,定时器仅用来模拟,在实际业务中应该替换为业务代码

                      const express = require('express');
                      const app = express();
                      const port = 3000;
                      // 设置响应头,表明这是一个SSE流
                      app.get('/events', (req, res) => {
                          res.setHeader('Content-Type','text/event-stream');
                          res.setHeader('Cache-Control', 'no-cache');
                          res.setHeader('Connection', 'keep-alive');
                          // 【注意:模拟定时发送数据(实际应用中需要根据真实业务逻辑触发发送)!!!】
                          const interval = setInterval(() => {
                              const data = `data: { "time": "${new Date().toLocaleString()}"}\n\n`;
                              res.write(data);
                          }, 3000);
                          // 当客户端关闭连接时,清除定时器
                          req.on('close', () => {
                              clearInterval(interval);
                          });
                      });
                      app.listen(port, () => {
                          console.log(`服务器运行在 http://localhost:${port}`);
                      });
                      
                      • 模拟客户端代码

                        mounted() {
                          const source = new EventSource('http://localhost:3000/events');
                          source.onmessage = (event) => {
                              const data = JSON.parse(event.data); // data 就是发送的消息
                              this.message = data.message;
                          };
                          source.onerror = (error) => {
                              console.log('SSE连接出错:', error);
                          };
                        }
                        
                        • 实现效果

                          前端数据实时更新的巧妙实现,大揭秘!

                          前端数据实时更新的巧妙实现,大揭秘!

                          3、特点

                          对于SSE来说,优势上在于简单易用,客户端用 EventSource可以接收,服务器端定期发数据即可推送。且单向通信、基于长连接,服务器资源消耗低。不过,SSE 是有局限的。只能够进行单向通信,现代浏览器大多支持,但是IE或者旧版本浏览器不支持。SSE 只支持文本格式的数据流,不支持二进制数据传输。总之,SSE 在合适场景能发挥优势,支撑实时数据推送需求。

                          五、总结

                          上述文章中,介绍了轮询、Web Socket、SSE 三种方案。适合的场景也有一些差别,例如:轮询的方式可以在更新频率不高的场景下使用、Web Socket可以在需要双向交互的场景下使用、SSE适用于服务器到客户端的单向数据流的场景下使用。开发者可以根据场景选择最优方案,提升产品实时交互体验。

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码