在日常开发中,消息推送是非常典型的业务需求,下面对消息推送简单的分析一下。
消息推送通常指网站的运营人员通过某种工具对用户当前网页或移动设备APP进行的主动消息推送。主要分为web端消息推送和移动端消息推送。

  • web端消息推送场景:站内信、未读邮件、监控报警;
  • 移动端消息推送场景:有人关注我的公众号,这时我就会收到一条推送消息,以此来吸引我点击打开应用;



消息推送通常在服务端会有若张消息推送表,用来记录用户触发不同事件所推送不同类型的消息,前端主动查询或者被动接收用户所有未读的消息。那么消息推送无非是推push和拉pull两种形式。下面看一下具体的实现方式。
1、短轮询
轮询应该是实现消息推送方案中最简单的一种。短轮询是指定时间间隔,由浏览器向服务器发出http请求,服务器实时返回未读消息给浏览器,浏览器渲染显示。一个简单的js定时器就可以搞定,每秒钟请求服务器一次未读消息接口,将返回数据显示即可。

setInterval(() => {// 方法请求message().then((res) => {if (res.code === 200) {this.messageData = res.data}})
}, 1000);

短轮询实现简单,效果还可以,缺点是由于推送数据并不会频繁变更,无论后端此时是否有新的消息产生,浏览器都会进行请求,势必对服务端造成很大的压力,浪费带宽和服务器资源。
2、长轮询
长轮询是对短轮询的一种改进,在尽可能减少对服务器资源的浪费的同时保证消息的相对实时性。如Nacos、Kafka、RocketMQ中都有用到长轮询。
实例:用长轮询实现消息推送
因为一个ID可能会被多个长轮询请求监听,所以采用了guava提供的Multimap结构存放长轮询,一个key可以对应多个value。
如页面发起长轮询请求,请求被挂起,若页面得到请求超时的状态码,则再次发起长轮询请求;当一旦监听到key发生变化,对应的所有长轮询都会得到响应(如数据变更的状态码),则主动请求未读消息的数据接口,渲染页面数据。

@RestController
@RequestMapping("/polling")
public class PollingController {// 存放监听某个Id的长轮询集合public static Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedMultimap(HashMultimap.create());/*** 设置监听*/@GetMapping(path = "watch/{id}")public DeferredResult<String> watch(@PathVariable String id) {// 对延迟对象设置超时时间DeferredResult<String> deferredResult = new DeferredResult<>(60 * 1000L);// 当延迟对象的异步请求完成时移除 key,防止内存溢出deferredResult.onCompletion(() -> {watchRequests.remove(id, deferredResult);});// 注册长轮询请求watchRequests.put(id, deferredResult);return deferredResult;}/*** 变更数据*/@GetMapping(path = "publish/{id}")public String publish(@PathVariable String id) {// 数据变更 取出监听ID的所有长轮询请求,并一一响应处理if (watchRequests.containsKey(id)) {Collection<DeferredResult<String>> deferredResults = watchRequests.get(id);for (DeferredResult<String> deferredResult : deferredResults) {deferredResult.setResult("数据已变更:200" + new Date());}}return "success";}}
@RestControllerAdvice
public class AsyncRequestTimeoutHandler {//@ResponseStatus(HttpStatus.NOT_MODIFIED)@ExceptionHandler(AsyncRequestTimeoutException.class)public String asyncRequestTimeoutHandler(AsyncRequestTimeoutException e) {System.out.println("异步请求超时:304" + new Date());return "异步请求超时:304" + new Date();}
}

当请求超过设置的超时时间,会抛出AsyncRequestTimeoutException异常,这里直接用@ControllerAdvice全局捕获统一返回即可,浏览器获取超时状态码后再次发起长轮询请求,如此往复调用。
长轮询相对于短轮询在性能上提升了,但依然会产生很多的请求,还是有不完美的地方。
3、SSE
SSE(Server-sent events),一种服务器发送事件。SSE它是基于HTTP协议的,我们知道一般意义上的HTTP协议是无法做到服务端主动向客户端推送消息的,但SSE是个例外,它变换了一种思路。
SSE在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而是text/event-stream类型的数据流信息,在有数据变更时从服务器流式传输到客户端。
整体的实现思路有点类似于在线视频播放,视频流会连续不断的推送到浏览器,你也可以理解成,客户端在完成一次用时很长(网络不畅)的下载。
4、MOTT
MQTT(Message Queue Telemetry Transport),一种基于发布/订阅(publish/subscribe)模式的轻量级通讯协议,通过订阅相应的主题来获取消息,是物联网(Internet of Thing)中的一个标准传输协议。
该协议将消息的发布者(publisher)与订阅者(subscriber)进行分离,因此可以在不可靠的网络环境中,为远程连接的设备提供可靠的消息服务,使用方式与传统的MQ有点类似。
TCP协议位于传输层,MQTT 协议位于应用层,MQTT 协议构建于TCP/IP协议上,也就是说只要支持TCP/IP协议栈的地方,都可以使用MQTT协议。
MQTT主要应用在物联网场景,首先HTTP协议它是一种同步协议,客户端请求后需要等待服务器的响应。而在物联网(IOT)环境中,设备会很受制于环境的影响,比如带宽低、网络延迟高、网络通信不稳定等,显然异步消息协议更为适合物联网应用程序。
HTTP是单向的,如果要获取消息客户端必须发起连接,而在物联网(IOT)应用程序中,设备或传感器往往都是客户端,这意味着它们无法被动地接收来自网络的命令。
5、Websocket
WebSocket是一种在TCP连接上进行全双工通信的协议,建立客户端和服务器之间的通信渠道。浏览器和服务器仅需一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
实例:springboot+websocket

     <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

配置类

@Configuration
@EnableWebSocket
public class WebSocketConfig {/*** 这个bean会自动注册使用了@ServerEndpoint注解声明的Websocket endpoint*/@Beanpublic ServerEndpointExporter serverEndpointExporter() {return new ServerEndpointExporter();}
}

websocket服务类

@Slf4j
@Component
@ServerEndpoint("/websocket/{userId}")
public class WebSocketServer {/*** 与客户端的连接会话,需要通过它来给客户端发送数据*/private Session session;/*** 存放客户端容器*/private static final CopyOnWriteArraySet<WebSocketServer> webSockets = new CopyOnWriteArraySet<>();/*** 用来存在线连接数*/private static final Map<String, Session> sessionPool = new HashMap<String, Session>();/*** 链接成功调用的方法*/@OnOpenpublic void onOpen(Session session, @PathParam(value = "userId") String userId) {try {this.session = session;webSockets.add(this);sessionPool.put(userId, session);log.info("websocket消息: 有新的连接,总数为:" + webSockets.size());} catch (Exception e) {}}/*** 收到客户端消息后调用的方法*/@OnMessagepublic void onMessage(String message) {log.info("websocket消息: 收到客户端消息:" + message);}/*** 此为单点消息*/public void sendOneMessage(String userId, String message) {Session session = sessionPool.get(userId);if (session != null && session.isOpen()) {try {log.info("websocket消: 单点消息:" + message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}/*** 此为单点消息(多人)*/public void sendMoreMessage(String[] userIds, String message) {for (String userId : userIds) {Session session = sessionPool.get(userId);if (session != null && session.isOpen()) {try {log.info("【websocket服务端】 单点消息:" + message);session.getAsyncRemote().sendText(message);} catch (Exception e) {e.printStackTrace();}}}}
}

测试服务端推送消息给前端

@CrossOrigin("*")
@RestController
@RequestMapping("/socket")
public class WebsocketController {@Resourceprivate WebSocketServer webSocketServer;/*** 发送消息*/@GetMapping(path = "publish")public String publish(String message, String userId) {//创建业务消息信息webSocketServer.sendOneMessage(userId, message);return "success";}
}

页面html

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="zh-CN"><head><title>未读消息</title><meta name="viewport" content="width=device-width, initial-scale=1.0"><script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js" type="text/javascript"></script><script>var ws = new WebSocket('ws://127.0.0.1:8080/websocket/10086');// 监听是否连接成功ws.onopen = function () {console.log('ws连接状态:' + ws.readyState);// 连接成功则发送一个数据ws.send('test');}// 接听服务器发回的信息并处理展示ws.onmessage = function (data) {$("#arrivedDiv").append("<br/>"+data.data);var count = $("#count").text();count = Number(count) + 1;$("#count").text(count);// 完成通信后关闭WebSocket连接//ws.close();}// 监听连接关闭事件ws.onclose = function () {// 监听整个过程中websocket的状态console.log('ws连接状态:' + ws.readyState);}// 监听并处理error事件ws.onerror = function (error) {console.log(error);}function sendMessage() {var content = $("#message").val();$.ajax({url: 'http:127.0.0.1:8080/socket/publish?userId=10086&message='+content,type: 'GET',success: function (data) {console.log(data)}})}</script>
</head><body>
<div><ul><li class="active"><i class="fa fa-home fa-lg"></i> 未读消息 <span id="count" class="unread">0</span></li></ul><div style="margin-left: 800px;"><input style="height: 25px; width: 180px;" maxlength="60" value="" id="message"/><button class="button" id="mySendBtn" onclick="sendMessage()"> 点击发送</button><div style="font-size: 20px;color: darkcyan"> 接收到的websocket消息</div><hr/><div id="arrivedDiv" style="height:200px; width:300px; overflow:scroll; background:#EEEEEE;"><br/></div></div>
</div>
</body></html>

测试效果如下

页面初始化建立websocket连接,浏览器和服务端就可以进行双向通信了。
在实际开发中,需要根据具体的业务场景选择消息推送的方式,需要思考自身业务的特点和场景需求,加油吧,少年。

Java之消息推送浅入浅出相关推荐

  1. java后台 flex前台例子_flex+blazeds+java后台消息推送(简单示例)

    现在有个问题需要大家思考一下,有个已经上线了的项目,有好好几千甚至上万的客户在使用了.现在项目开发商想发布一个通知.在今天下午6点需要重新启动服务器,想让在线的人在在预定的时间内都收到消息,让大家做好 ...

  2. 个推+mui+html5 +java完成消息推送

    几个月前写的,个推官方的例子真是难,找了好久才拼出来这几个方法,本文包含java调用个推SDK的消息推送和app方向的接收消息. package geti; import java.io.IOExce ...

  3. java xmpp消息推送_基于XMPP协议(openfire服务器)的消息推送实现

    最近好像有不少朋友关注Android客户端消息推送的实现,我在之前的项目中用到过Java PC客户端消息推送,从原理讲上应该是一致的,在这里分享一下个人的心得. 消息推送实现原理 这里的消息推送,通常 ...

  4. java整合消息推送_SpringMVC整合websocket实现消息推送及触发功能

    本文为大家分享了SpringMVC整合websocket实现消息推送,供大家参考,具体内容如下 1.创建websocket握手协议的后台 (1)HandShake的实现类 /** *Project N ...

  5. java android消息推送_Android中使用socket通信实现消息推送的方法详解

    原理最近用socket写了一个消息推送的demo,在这里和大家分享一下. 主要实现了:一台手机向另外一台手机发送消息,这两台手机可以随时自由发送文本消息进行通信,类似我们常用的QQ. 效果图: 原理: ...

  6. Java xinge 消息推送Android和IOs

    xinge消息推送: 比如就像淘宝一样,用户下完订单,就会给用户提示一条信息:订单核对等等.如果我们也想实现类似的功能呢? 一.往pom.xml文件中引入包 <dependency>< ...

  7. 【java】java天气消息推送至微信公众号详细教程

    文章目录 读前必看 测试号推送 天气接口获取数据 谁说程序员不懂浪漫? 将你的关心 推送至微信公众号 给女朋友及时的关怀~(这位同学 你女朋友呢?) 读前必看 关于微信开发平台,小程序和公众号是不一样 ...

  8. java即时消息推送

    整个例子的源码下载:http://pan.baidu.com/s/1gfFYSbp 下载服务端jar文件 Comet4J目前仅支持Tomcat6.7版本,根据您所使用的Tomcat版本下载[comet ...

  9. Java 苹果消息推送

    一: IOS: 1.引入依赖 <dependency><groupId>com.github.fernandospr</groupId><artifactId ...

最新文章

  1. [转] DataSet的的几种遍历
  2. Linux下进程间通信的六种机制详解
  3. 深度学习小技巧(二):如何保存和恢复scikit-learn训练的模型
  4. unzip 分包_建筑总包企业计量分包成本时,未取得相应分包发票,增值税如何处理?...
  5. springboot整合postgre和hbase实现互相交互功能
  6. 群人各说什么是哈希算法?
  7. Python 中操作 MySQL 步骤
  8. Shell awk文本处理,shell脚本编写
  9. python return用法_初学Python要了解什么 装饰器知识汇总有哪些
  10. C#解析JSON数据
  11. 心情随笔(五):九月依然精彩
  12. 软件测试基础知识【归纳】
  13. 大淘宝的终极商业阶段
  14. XML内容要有根标签:Extra content at the end of the document
  15. 网易云信短信功能使用
  16. 计算机双显卡关掉一个,怎么关掉集成显卡_win10双显卡电脑关闭集成显卡的方法...
  17. 服务器软硬件安装和配置,Windows Server 2016-系统安装软硬件要求
  18. linux gianfar 网口驱动源码,基于MPC8313ERDB平台的Marvell88E1111型网卡驱动移植(uboot+kernel)...
  19. 软件工程中国学科排名——2021软科
  20. R语言:填色等值线图及其色标(color bar)设置

热门文章

  1. 节点污点 Taint 和容忍度 Toleration在生产中的使用
  2. 如何搭建一个站内搜索引擎(一) 第1章 写在最前
  3. mac下Preferences丢失的问题
  4. java开发工具(3)你真的会用IDEA么?(下)keyMap、Project Structure功能介绍
  5. 搭建手机文件服务器,普通用户的低成本家庭文件服务器(伪NAS)的搭建(手机备份篇)...
  6. Linux如何使用WIFI连接abd
  7. 卸载landesk的方法
  8. 各种抠图动态图片_抠图动画
  9. LLJ-300HS;LLJ-F(S)系列漏电继电器
  10. 南京邮电大学操作系统实验四:简单文件系统模拟实验