大家好,我是符晓~
我有个朋友~

我做了一个小网站,现在想在一个网站中实现网页消息推送的功能。没错,就是下图的小红点,很常见的功能。
但是,他还没想好怎么做。在这里,我帮他整理了几个方案,简单实施了一下。
下载案例,还记得Star吗
什么是消息推送?
有很多推送场景。比如有人关注我的微信官方账号,我就会收到一条推送消息,吸引我点击打开应用。
消息推送;颜色:rgbborder:1px solid RGB " data-from-paste = " 1 " data-diagnostic-> push)通常是指网站运营人员通过某种工具对用户当前网页或移动设备APP进行的主动消息推送。
消息推送一般分为web端消息推送和移动端消息推送。
上面这个属于移动消息推送,常用的有web消息推送,比如站内消息、未读邮件数、监控报警数等。
在具体实现之前,我们先分析一下前面的需求。其实功能很简单。只要有事件触发,网页的通知小红点就会实时+1。
通常服务器上有几个消息推送表,用来记录用户触发不同事件时推送的不同类型的消息。前端主动查询或被动接收用户的所有未读消息。
推无非就是推拉。让我们一个一个来看看。
短期投票
投票;颜色:rgbborder:1px solid RGB " data-from-paste = " 1 " data-diagnose-> polling)应该是实现消息推送最简单的方案。这里我们暂且将轮询分为短轮询和长轮询。
短轮询很容易理解。在指定的时间间隔,浏览器向服务器发送HTTP请求,服务器将未读消息数据实时返回给客户端,然后浏览器渲染显示。
一个简单的JS定时器就可以做到。每秒请求一次未读消息的界面,返回的数据可以显示。
SetInterval => {//方法请求message count . then = > { if { this . message count = RES . data } },1000);
效果还是可以达到的。短轮询很简单,但它的缺点也很明显。由于推送数据不会频繁变化,客户端无论此时后端是否有新消息生成都会发出请求,这必然会对服务器造成很大压力,浪费带宽和服务器资源。
长轮询
长轮询是上短轮询的改进版本,可以尽可能减少服务器资源的浪费,保证消息的相对实时性。长轮询广泛应用于中间件,如Nacos和apollo配置中心,消息队列kafka和RocketMQ。
Nacos配置中心的交互模式是推还是拉?在这篇文章中,我详细介绍了Nacos长轮询的实现原理,感兴趣的朋友可以看看。
这次我用apollo Configuration Center实现了长轮询,应用了一个类DeferredResult,这是servelet3.0之后Spring封装提供的异步请求机制,字面意思是延迟结果。
DeferredResult可以让容器线程在不阻塞请求线程的情况下快速释放被占用的资源,从而接受更多的请求,提高系统的吞吐量。然后启动异步工作线程处理真正的业务逻辑,处理完成后调用DeferredResult.setResult提交响应结果。
让我们使用长轮询来推送消息。
因为一个ID可能被多个长轮询请求监控,所以我使用guava包提供的Multimap结构来存储长轮询,一个键可以对应多个值。一旦监控到密钥发生变化,所有相应的长轮询都会响应。获取前端主动超时的状态码,了解数据变化,主动查询未读消息的界面,更新页面数据。
@ Controller @ requestmapping public类轮询控制器{//存储侦听Id的长轮询集。//线程同步结构public static multimap > watch requests = multimaps . synchronized multimap);@ get mapping @ response body public deferred result watch {//delay object set time deferred result deferred result = new deferred result;//异步请求完成时移除键,防止内存溢出deferred result . on completion-> { watch requests . Remove;});//注册器轮询请求watchRequests.put返回deferredResult}@ get mapping @ response body public string publish {//data change取出监控ID的所有长轮询请求,并逐一响应和处理,如果){ collection preferred results = watch requests . get;for { deferred result . setresult);} }返回“成功”;}折叠
当请求超过设置的超时时间时,将抛出AsyncRequestTimeoutException异常。这里只需要用@ControllerAdvice全局统一捕捉和返回即可。前端获得约定的状态码后,再次发起长轮询请求,以此类推。
@ControllerAdvicepublic类AsyncRequestTimeoutHandler { @ response status @ response body @ exception handler公共字符串AsyncRequestTimeoutHandler { system . out . println;返回“304”;}}
我们来测试一下。首先,页面发起一个长轮询请求/polling/watch/10086。消息改变,请求挂起,数据直到超时才改变,长轮询请求再次发起;手动更改数据/polling/publish/10086后立即应答长轮询,完成前端处理业务逻辑,再次发起请求,以此类推。
与短轮询相比,长轮询大大提高了性能,但它仍然会产生更多的请求,这是它的一个不完善之处。
Iframe流
iframe流程是在页面中插入一个隐藏标签,通过请求src中的消息号API接口,在服务器和客户端之间建立一个长连接,服务器不断向iframe传输数据。
传输的数据通常是HTML或嵌入的javascript脚本,以实时更新页面。
这种方法实现简单,前端只需要一个标签。服务器直接组装html和js脚本数据,写入response。
@ Controller @ requestmapping public类iframe Controller { @ get mapping public void message抛出IOException,interrupted exception { while { response . set header;response . set date header;response.setHeaderresponse.setStatusresponse . get writer . print . innerhtml = "+count . get+" ";"+" parent . document . getelementbyid . innerhtml = "+count . get+" ";" + "");} }}
但是我个人不推荐,因为会在浏览器上显示请求未加载,图标会不停旋转。简直就是强迫症杀手。
同SOUTH-SOUTH-EAST
很多人可能不知道,服务器把消息推送到客户端。其实除了大家熟悉的WebSocket的机制之外,还有一个发送事件的服务器。颜色:rgbborder:1px solid RGB " data-from-paste = " 1 " data-diagnose-> server-sent events),简称SSE。
SSE是基于HTTP协议的。我们知道HTTP协议一般情况下是无法让服务器主动向客户端推送消息的,但是SSE是个例外,这改变了一种思维方式。
SSE在服务器和客户端之间打开一个单向通道。服务器响应文本/事件流类型的数据流信息,而不是一次性数据包,当数据发生变化时,数据流信息从服务器流向客户端。
整体实现思路有点类似于在线视频播放,视频流会被持续推送到浏览器。你也可以理解为客户端完成一个很长的下载。
类似于SSE WebSocket,可以建立服务器和浏览器之间的通信,从服务器向客户端推送消息,但还是有一些区别:

SSE基于HTTP协议,它们不需要特殊的协议或服务器实现就可以工作;WebSocket需要一个单独的服务器来处理协议。
SSE单向通信,只是从服务器到客户端的单向通信;WebSocket全双工通信,即通信双方可以同时发送和接收信息。
SSE实现简单,开发成本低,无需引入其他组件;WebSocket传输数据需要进行两次分析,开发门槛较高。
SSE默认支持断开连接和重新连接;WebSocket需要自己实现。
SSE只能传输文本消息,二进制数据需要编码后才能传输;WebSocket默认支持传输二进制数据。
如何选择SSE和WebSocket?
技术没有好坏,只有哪个更适合。
SSE似乎不太为人所知,部分原因是WebSockets的出现,它提供了更丰富的协议来实现双向和全双工通信。对于游戏、即时通讯和需要双向近实时更新的场景,拥有双向通道更有吸引力。但是,在某些情况下,没有必要从客户端发送数据。您只需要对服务器操作进行一些更新。比如SEE在实现的难度和成本上更有优势,比如站内消息、未读消息数、状态更新、股票价格、监控人数等。除此之外,SSE还有很多WebSockets在设计上缺乏的功能,比如自动重连、事件ID以及发送任意事件的能力。
前端只需要做一个HTTP请求,带一个唯一的ID,打开事件流,监听服务器推送的事件。
服务器的实现更简单。创建一个SseEmitter对象,并将其放入sseEmitterMap进行管理。
私有静态映射sseEmitterMap = new concurrent hashmap;public static SSE发射器连接{ try {//设置超时,0表示不会过期。默认为30秒SSE发射器SSE发射器=新SSE发射器;//注册回调SSE emitter . on completion);SSE emitter . on error);SSE emitter . on time out);sseEmitterMap.putcount.getAndIncrement返回sseEmitter} catch { log.info}返回null}public static void sendmessage { if){ try { sseemittermap . get . Send;} catch { log . error);removeUser}}}折叠
我们模拟服务器推送消息,看到客户端已经收到消息,和我们预期的效果一致。
注:SSE不支持IE浏览器,在兼容其他主流浏览器方面做得不错。
推送消息
什么是MQTT协议?
MQTT全称:基于发布/订阅模式的轻量级通信协议,通过订阅相应的主题来获取消息,是物联网中的标准传输协议。
该协议分离了消息的发布者和订阅者,因此它可以在不可靠的网络环境中为远程连接的设备提供可靠的消息服务,这有点类似于传统的MQ。
TCP在传输层,MQTT在应用层,MQTT建立在TCP/IP协议之上,也就是说只要支持TCP/IP协议栈的地方都可以使用MQTT。
为什么要使用MQTT协议?
为什么MQTT在物联网中如此受欢迎?而不是其他协议,比如大家比较熟悉的HTTP协议?
首先,HTTP协议是同步协议。客户端请求后,需要等待服务器的响应。然而,在物联网环境中,设备会受到环境的影响,如低带宽、高网络延迟、网络通信不稳定等。显然,异步消息协议更适合IOT应用。
HTTP是单向的。如果您想要获取消息,客户端必须启动连接。在物联网应用中,设备或传感器往往是客户端,这意味着它们不能被动地接收来自网络的命令。
通常,需要向网络上的所有设备发送命令或消息。HTTP要实现这样的功能不仅很难,而且极其昂贵。
这里不再重复MQTT协议的介绍和实践。可以参考我之前的两篇文章,都很详细。
MQTT协议简介
没想到springboot+rabbitmq作为智能家居这么简单。
MQTT实现消息推送
未读消息,前端和RabbitMQ实时消息推送练习,贼简单~
求转发到
Websocket应该是大家比较熟悉的推送消息的方式。我们在讲SSE的时候也和websocket做了比较。
WebSocket是一种基于TCP连接的全双工通信协议,它建立了客户端和服务器之间的通信通道。浏览器和服务器只需要握手一次,两者之间就可以直接建立持久连接,进行双向数据传输。
Springboot整合了websocket,首先推出了websocket相关的工具包,比SSE贵。
org.springframework.boot
使用server @ServerEndpoint批注标记当前类是websocket服务器,客户端可以通过WS://localhost:7777/WebSocket/10086连接到web socket服务器。
@ component @ slf4j @ servendpointpublic class websocketserver {//需要使用与客户端的连接会话向客户端私有会话session发送数据;private static final CopyonWriteArraySet web socket = new CopyOnWriteArraySet;//private static final map session pool = new hashmap为现有线路连接数;@ on open public void){ try { this . session = session;webSockets.addsessionPool.putlog . info);} catch {}} @ on message public void on message { log . info;}public void sendonemamessage { session session = session pool . get;if){ try { log . info;session . getasynchremote . send text;} catch { e.printStackTrace} } }}
前端初始化WebSocket连接,监控连接状态,接收或发送数据到服务器。
页面初始化建立websocket连接后,可以进行双向通信,效果还不错。
自定义推送
上面我们已经给了我六种方案的原理和代码实现,但是在实际的业务开发过程中,不要盲目的直接使用,要根据自身系统业务的特点和实际场景来选择合适的方案。
最直接的推送方式就是使用第三推送平台。毕竟有钱能解决的需求不是问题。无需复杂的开发和操作即可直接使用,省时、省力、省心。像goEasy,Aurora Push之类的东西都是非常好的三方服务商。
一般大公司都有自己开发的消息推送平台。比如我们网站里的消息,只是平台上的一个接触点,短信、邮件、微信微信官方账号、小程序,所有能接触到用户的渠道都可以接触到。

推送系统内部相当复杂,比如消息内容的维护和审核、推送人群的圈定、到达过滤和拦截、推送失败补偿等。还有很多场景在技术上涉及到大量数据和高并发。所以今天的执行在这个庞大的系统面前只是小问题。
Github地址
文中提到的案例我都实现了,整理出来放在Github上。如果你觉得它们有用,就开始吧!
门户:https://github . com/cheng xy-NDS/spring boot-notebook/tree/master/spring boot-real time-data
资料来源:file/tupian/20220929/16495096.html

