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

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

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

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

其中的代码也上传到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
ai智能写作工具(AI写作生成器下载:让软文写作更高效更智能) 服务器迁移要多长时间(美国服务器迁移时怎么测试)