记录--服务端推送到Web前端有哪几种方式?

news2024/11/18 9:47:40

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

这个问题?

这个问题一般会出现在面试题里面,然后回答一些诸如轮询、WebSocket之类的答案。当然,实际开发中,也会遇到类似别人给你赞了,要通知给你的情况。这时服务端推送给Web前端(先局限在Web前端,毕竟其他端还有一些特殊方法)到底有多少种方法?它们到底是怎么实现的?

写个Demo看看吧,这样正好把主要(不清楚是否还有漏的)的方案都实现一遍。先看效果:

其中的代码也上传到GitHub了,在server-push( github.com/waiter/serv… )这里。

各种方案

从上面的截图也已经可以看出,本文主要写了5种方案,那么接下来也就一个一个简单介绍一下吧。

另外,本文涉及的Demo,后端直接使用原生的Node.js开发,没有使用KoaExpress之类的,也没有使用额外的库,类似socket.io,主要是想保持最精简的状态来呈现。前端也只是在最基础的HTML上,引入了jQuery来方便做DOM操作,也引入了Bootstrap来快速实现统一的样式,而未再引入类似VueReact之类的框架。

还有,为了触发服务端推送,这边在前端页面上加了个输入框和按钮,来将消息发送给后端,后端会缓存消息,并触发推送,后端大体代码类似:

// 缓存需要推送的信息
const datas = [];
// 各种方案触发推送时的回调
const callbacks = {};

// 注册接口回调
server.on('request', (req, res) => {
    const { pathname, query } = parse(req.url, true);
    // 如果发现是前端触发推送接口
    if (pathname === '/api/push') {
      if (query.info) {
        // 缓存推送信息
        datas.push(query.info);
        const d = JSON.stringify([query.info]);
        // 触发所有推送回调
        Object.keys(callbacks).forEach(k => callbacks[k](d));
      }
      res.end('ok');
    }
});

1. 轮询(短轮询)

这是最简单直观的方法,就是每隔一段时间发起一个请求到后端询问是否有新信息。至于为什么又叫短轮询,其是相对于后续要说的长轮询来对比的。

这样前端只要设置一个setTimeout来定时请求就行:

// 缓存前端已经获取的最新id
let id = 0;

function poll() {
  $.ajax({
    url: '/api/polling',
    data: { id },
    }).done(res => {
      id += res.length;
    }).always(() => {
      // 10s后再次请求
      setTimeout(poll, 10000);
    });
}

poll();

后端也是否简单,根据前端给到的id,看看有没有新消息,有就返回,没有就返回空

const id = parseInt(query.id || '0', 10) || 0;
res.writeHead(200, { 'Content-Type': 'application/json;' });
res.end(JSON.stringify(datas.slice(id)));

这个看起来其实时性与请求频率成正相关,但是当请求频率上来了,性能浪费也就越高,毕竟可能大部分请求都是无意义的。

2. 长轮询

在翻找资料的时候,发现有些资料会直接把这个当作短轮询,有点匪夷所思。这里的长轮询相对前面的轮询来说,算是一种优化。具体就是前端发起请求到后端,后端不直接返回,而是等待有新信息时再返回。所以这样发起的一个请求,可能需要很长的时间才能等到返回,故而叫做长轮询。

其前端代码基本和短轮询一致,只不过把请求的超时时间设置较长(比如1分钟),然后无论请求成功或失败,马上再次发起请求即可。

相对来说,后端的写法就要稍微改动一下

const id = parseInt(query.id || '0', 10) || 0;
const cbk = 'long-polling';
delete callbacks[cbk];
const data = datas.slice(id);
res.writeHead(200, { 'Content-Type': 'application/json' });
// 发起请求时,正好有新消息就返回
if (data.length) {
  return res.end(JSON.stringify(data));
}
req.on('close', () => {
  delete callbacks[cbk];
});
// 注册新消息回调
callbacks[cbk] = (d) => {
  res.end(d);
};

这样,**相对于短轮询,少了很多无意义的请求,而且消息的实时性也非常好。**不过,当服务端有异常时,会导致长轮询短时间内不断发起请求,可能让服务端承受更大的压力,所以两次长轮询之间最好有一定间隔,或者异常检测机制。

3. SSE(Server-sent events)

Traditionally, a web page has to send a request to the server to receive new data; that is, the page requests data from the server. With server-sent events, it's possible for a server to send new data to a web page at any time, by pushing messages to the web page. These incoming messages can be treated as Events + data inside the web page.

前面提到的轮询、长轮询都是一问一答式的,一次请求,无法推送多次消息到前端。而SSE就厉害了,一次请求,N次推送

其原理,或者说类比,个人认为可以理解为下载一个巨大的文件,文件的内容分块传给前端,每块就是一次消息推送

听起来很厉害,先看看后端代码要怎么写

const cbk = 'sse';
delete callbacks[cbk];
res.writeHead(200, {
  // 这个是核心
  'Content-Type': 'text/event-stream',
  'Connection': 'keep-alive',
});
// 把缓存的信息推送给前端
res.write(`data: ${JSON.stringify(datas)}\n\n`);
// 注册新消息回调
callbacks[cbk] = (d) => {
  res.write(`data: ${d}\n\n`);
};
req.on('close', () => {
  delete callbacks[cbk];
});

后端代码很简单,核心在于Content-Type: text/event-stream,这要让前端知道这是SSE,还有就是传输信息的格式比较特别一点,详细的可以看 MDN( developer.mozilla.org/en-US/docs/… )

而前端有专门的EventSource来接收,使用起来很方便

const es = new EventSource('/api/sse');
es.onmessage = (e) => {
   try {
    const c = JSON.parse(e.data);
    } catch (err) {
      console.log(err);
    }
}

这样就好了,如果你打开Chrome的开发者工具中的网络标签,你就会发现Chrome对于SSE请求,有专门的展示标签

另外,**SSE还支持自动重连!**服务器短时间异常,恢复之后,无需额外代码,SSE就自动重连上了。不过,本人在实际工作中却没有碰到过SSE,也就在面试题中见过。

4. WebSocket

既然有了SSE,那还要WebSocket干啥啊?因为WebSocket可以一次连接,双向推送,而SSE只能从服务端推送到前端。从这个角度来看,用WebSocket来单做服务端推送,有点大材小用了。

另外,初见WebSocket,可能会对其与Socket的联系有点疑惑。Socket协议是与HTTP协议平级的,而WebSocket协议是基于HTTP协议的,不过两者在使用层面上是十分相近的。

其前端使用写法与SSE类似,十分简单,只不过请求链接为ws://或者wss://开头(相当于http://https://)

const ws = new WebSocket('ws://localhost:3000/ws');
ws.onmessage = e => {
  try {
    const c = JSON.parse(e.data);
    } catch (err) {
      console.log(err);
    }
};

而如果要用原生Node.js来写WebSocket服务,就会麻烦一些了,一般情况都会使用类似socket.io之类的三方库来降低实现成本。这边也就在网上摘抄了一段代码来简单实现一下,详细的可以看Github上的Demo代码

server.on('upgrade', (req, socket) => {
  const cbk = 'ws';
  delete callbacks[cbk];
  const acceptKey = req.headers['sec-websocket-key']; 
  const hash = generateAcceptValue(acceptKey); 
  const responseHeaders = [ 'HTTP/1.1 101 Web Socket Protocol Handshake', 'Upgrade: WebSocket', 'Connection: Upgrade', `Sec-WebSocket-Accept: ${hash}` ];
  // 告知前端这是WebSocket协议
  socket.write(responseHeaders.join('\r\n') + '\r\n\r\n');
  // 发送数据
  socket.write(constructReply(datas));
  callbacks[cbk] = (d) => {
    socket.write(constructReply(d));
    }
    socket.on('close', () => {
      delete callbacks[cbk];
    });
});

这个在Chrome浏览器中,也有专门的标签页展示

不过,它没有像SSE一样有自动重连,这块需要自行实现。

一般网页实时聊天之类需要双向推送的,都会使用WebSocket来实现。

5. iFrame

这算是找资料的时候意外发现的,之前并不知道还有这样的玩法。原理类似使用iFrame加载一个巨大的网页,利用浏览器会一边加载一边解析执行返回的HTML,通过分次返回Script标签来实现消息推送。其实现类似SSE,不过看起来就比较==hack==。

前端代码很简单,只不过要注册一个回调给iframe使用

// 注册给iframe使用的方法
window.change = function(data) {

};
$('body').append('<iframe src="/api/iframe"></iframe>');

而后端也很简单,有消息的时候返回script标签即可

const cbk = 'iframe';
delete callbacks[cbk];
// 返回缓存信息
res.write(`<script>window.parent.change(${JSON.stringify(datas)});</script>`);
callbacks[cbk] = (d) => {
  res.write(`<script>window.parent.change(${d});</script>`);
};
req.on('close', () => {
  delete callbacks[cbk];
});

相当奇淫巧技了。不过,似乎没找到怎么判断加载异常的情况,可能需要自行加心跳来实现了

另外,很多文章在说使用iFrame方法时,会导致浏览器显示未加载完,图标一直转的样子。但是个人认为,图标一直转是因为页面一直没有onload,那么在页面onload之后,再创建iFrame就应该没有这个问题了

总结一下

上面实现了5种推送的方案,弄了一个表格简单对比一下

方案()实时单次连接自动重连断线检测双向推送无跨域
短轮询
长轮询
SSE
WebSocket
iFrame

本文转载于:

https://juejin.cn/post/7113813187727720461

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/398382.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

华为OD机试题,用 Java 解【一种字符串压缩表示的解压】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

20230309英语学习

What Is Sleep Talking? We Look at the Science 为什么人睡觉会说梦话&#xff1f;来看看科学咋说 Nearly everyone has a story about people talking in their sleep.Though it tends to be more common in children, it can happen at any age:A 2010 study in the jour…

如何恢复已清空的 Windows 回收站?

Windows 95 中引入的回收站文件夹&#xff08;也称为垃圾桶&#xff09;是有史以来最有用的功能之一&#xff0c;可以保护您免受自己的错误和粗心大意的影响。 通常&#xff0c;从文件夹中恢复丢失的文件就像单击桌面上的回收站图标并执行简单的拖放操作一样简单。 但是&…

Java实例实验项目大全源码企业通讯打印系统计划酒店图书学生管理进销存商城门户网站五子棋

wx供重浩&#xff1a;创享日记 对话框发送&#xff1a;java实例 获取完整源码源文件视频讲解文档资料等 文章目录1、企业通讯2、快递打印系统3、开发计划管理系统4、酒店管理系统5、图书馆管理系统6、学生成绩管理系统7、进销存管理系统8、神奇Book——图书商城9、企业门户网站…

数据库管理-第六十期 监听(20230309)

数据库管理 2023-03-09第六十期期 监听1 无法访问2 监听配置3 问题复现与解决4 静态监听5 记不住配置咋整总结第六十期期 监听 不知不觉又来到了一个整10期数&#xff0c;我承认上一期有很大的划水的。。。嫌疑吧&#xff0c;本期内容是从帮群友解决ADG前置配置时候的一个问题…

C51---定时器中断相关寄存器

1.中断系统&#xff0c;是为使CPU具有对外界紧急事件的实时处理能力而设置的。 当中央处理器CPU正在处理某件事情的时候&#xff0c;要求CPU暂停当前任务或工作&#xff0c;转而去处理这这个紧急事件。处理完以后&#xff0c;再回到原来的被中断的地方&#xff0c;继续原来的工…

华为OD机试题,用 Java 解【寻找相同子串】问题

华为Od必看系列 华为OD机试 全流程解析+经验分享,题型分享,防作弊指南)华为od机试,独家整理 已参加机试人员的实战技巧华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典使用说明 参加华为od机试,一定要注意不…

RoCEv2网络部署实践

延续上篇RoCE网络的介绍&#xff0c;我们知道承载ROCEv2流量必须有一张无损网络。 本章主要介绍在以太网环境部署无损网络的关键点。 首先是QoS&#xff0c;包含流分类和队列调度两部分。 流分类&#xff1a;在网络接入设备&#xff08;TOR&#xff09;配置if-match类的语句&am…

一本通 2.8.1 广度优先搜索算法

1329&#xff1a;【例8.2】细胞 【题目描述】 一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如:阵列 有4个细胞。 【题目分析】 遍历所有节点&#xff0c;当无标识且不为零&#xff0c;…

「Vue面试题」动态给vue的data添加一个新的属性时会发生什么?怎样去解决的?

一、直接添加属性的问题 我们从一个例子开始 定义一个p标签&#xff0c;通过v-for指令进行遍历 然后给botton标签绑定点击事件&#xff0c;我们预期点击按钮时&#xff0c;数据新增一个属性&#xff0c;界面也 新增一行 <p v-for"(value,key) in item" :key&q…

Esp8266学习4. 基于Arduino的PWM与红外信号处理

Esp8266学习4. 基于Arduino的PWM与红外信号处理一、基本概念1. PWM2. ESP8266 的 PWM功能3. node-mcu 引脚图4. 模拟写入&#xff08;1&#xff09;analogWrite&#xff08;2&#xff09;修改频率 analogWriteFreq&#xff08;3&#xff09;调节分辨率二、使用 analogWrite实现…

思腾合力深思系列 | 四款高性能 AI 服务器

深思系列 AI 服务器涵盖多种 CPU 平台&#xff0c;支持按客户需求预装 OS、驱动、DL 框架、常用 DL 库&#xff0c;节省您大量的前期调试时间&#xff0c;开机即用。 一个简单的任务&#xff0c;若想要在 AI 的脑中形成清晰的思路&#xff0c;需要大量的实验和练习。从 AI 训练…

05-CSS

今日目标能够说出 为什么要用定位能够说出 定位的 4 种分类能够说出 4 种定位各自的特点能够说出 为什么常用子绝父相布局能够写出 淘宝轮播图布局能够说出 显示隐藏的 3 种方式以及区别1. 定位(position) 介绍1.1 为什么使用定位我们先来看一个效果&#xff0c;同时思考一下用…

ctfshow_crypto_妈呀完了writeup

目录一、题目原题二、解题步骤1.去掉01串中的空格2.把01二进制串转换成十进制数3.将十进制整数转换成bytes三、后记一、题目原题 题目给了一串01和一个png图片&#xff0c;打开看了提示说“图文无关”&#xff1a; 图片附件如下&#xff1a; 二、解题步骤 1.去掉01串中的空…

3·8 妇女节特别策划 | 对话开源社里的“半边天”:多彩的她们,有别样的力量~...

三八妇女节Womens Day< 2023/03/08 >01职场女性 陈阳开源社理事长、微软云计算机与人工智能事业部首席产品经理一句话介绍自己&#xff1a;开源社区中的二进制女性向上滑动阅览采访Q1&#xff1a;能介绍一下自己目前正在做的职业或者项目嘛&#xff1f;白天在微软云计算和…

Oracle调优日记

Oracle调优日记前言前置知识联表查询Inner Joinleft joinright join环境背景描述问题展示最初代码问题解决过程优化一优化二优化三接口测试检查数据库连接总结前言 很难想想会在600条数据和4万条数据的两张表联表查询的的情况下&#xff0c;查询花了10多s。这里记录一下排查过…

PMP考试是什么?介绍+资料分享

我来介绍一下PMP&#xff1a; PMP考试是由PMI组织和出题&#xff0c;严格评估项目管理人员知识技能是否具有高品质的资格认证考试。 1999年&#xff0c;PMP考试在所有认证考试中第一个获得ISO9001国际质量认证,从而成为全球权威的认证考试之一。下载PMP备考资料可看封面或在文…

二叉树经典14题——初学二叉树必会的简单题

此篇皆为leetcode、牛客中的简单题型和二叉树基础操作&#xff0c;无需做过多讲解&#xff0c;仅付最优解。有需要的小伙伴直接私信我~ 目录 1.二叉树的节点个数 2.二叉树叶子节点个数 3.二叉树第K层节点个数 4.查找值为X的节点 5.leetcode——二叉树的最大深度 6.leetc…

Spark(5):RDD概述

目录 0. 相关文章链接 1. 什么是RDD 2. RDD核心属性 3. 执行原理 0. 相关文章链接 Spark文章汇总 1. 什么是RDD RDD&#xff08;Resilient Distributed Dataset&#xff09;叫做弹性分布式数据集&#xff0c;是 Spark 中最基本的数据处理模型。代码中是一个抽象类&#x…

淘宝widget链路方案总结

目前widget生态已经做了大量的基建工作,同时在widget生态的演进过程中我们发现如何匹配用户的偏好一直以来是一个挑战工作&#xff0c;本文介绍了widget的整体链路。业务背景▐ widget介绍2020年底iOS推出了新版widget之后引起了一些声浪&#xff0c;但仍然很多苹果用户并不了…