首页IT科技服务器推送消息至浏览器(记录–服务端推送到Web前端有哪几种方式?)

服务器推送消息至浏览器(记录–服务端推送到Web前端有哪几种方式?)

时间2025-08-01 13:12:37分类IT科技浏览4075
导读:这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助...

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

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

各种方案

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

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

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

// 缓存需要推送的信息 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, its 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="https://www.cnblogs.com/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

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

声明:本站所有文章,如无特殊说明或标注                 ,均为本站原创发布        。任何个人或组织                     ,在未征得本站同意时   ,禁止复制           、盗用                  、采集           、发布本站内容到任何网站        、书籍等各类媒体平台           。如若本站内容侵犯了原著者的合法权益             ,可联系我们进行处理                  。

创心域SEO版权声明:以上内容作者已申请原创保护,未经允许不得转载,侵权必究!授权事宜、对本内容有异议或投诉,敬请联系网站管理员,我们将尽快回复您,谢谢合作!

展开全文READ MORE
vue框架基础知识(Vue框架快速上手) 网站排名三点抓权重,助力流量激增(实用技巧分享,帮你快速提升网站排名)