当前位置:网站首页>实现web实时消息推送的7种方案
实现web实时消息推送的7种方案
2022-07-30 16:36:00 【InfoQ】


什么是消息推送(push)
pushweb端消息推送移动端消息推送

+1
pushpull短轮询
polling短轮询长轮询HTTPsetInterval(() => {
// 方法请求
messageCount().then((res) => {
if (res.code === 200) {
this.messageCount = res.data
}
})
}, 1000);
长轮询
NacosapollokafkaRocketMQapolloDeferredResultservelet3.0
DeferredResultDeferredResult.setResult(200)guavaMultimap@[email protected]("/polling")public class PollingController {
// 存放监听某个Id的长轮询集合
// 线程同步结构
public static Multimap<String, DeferredResult<String>> watchRequests = Multimaps.synchronizedMultimap(HashMultimap.create());
/**
* 公众号:程序员小富
* 设置监听
*/
@GetMapping(path = "watch/{id}")
@ResponseBody
public DeferredResult<String> watch(@PathVariable String id) {
// 延迟对象设置超时时间
DeferredResult<String> deferredResult = new DeferredResult<>(TIME_OUT);
// 异步请求完成时移除 key,防止内存溢出
deferredResult.onCompletion(() -> {
watchRequests.remove(id, deferredResult);
});
// 注册长轮询请求
watchRequests.put(id, deferredResult);
return deferredResult;
}
/**
* 公众号:程序员小富
* 变更数据
*/
@GetMapping(path = "publish/{id}")
@ResponseBody
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("我更新了" + new Date());
}
}
return "success";
}AsyncRequestTimeoutException@ControllerAdvice@ControllerAdvicepublic class AsyncRequestTimeoutHandler {
@ResponseStatus(HttpStatus.NOT_MODIFIED)
@ResponseBody
@ExceptionHandler(AsyncRequestTimeoutException.class)
public String asyncRequestTimeoutHandler(AsyncRequestTimeoutException e) {
System.out.println("异步请求超时");
return "304";
}
/polling/watch/10086/polling/publish/10086
iframe流
<iframe>srciframeHTMLjavascript
<iframe><iframe src="/iframe/message" style="display:none"></iframe>response@Controller
@RequestMapping("/iframe")
public class IframeController {
@GetMapping(path = "message")
public void message(HttpServletResponse response) throws IOException, InterruptedException {
while (true) {
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
response.setHeader("Cache-Control", "no-cache,no-store");
response.setStatus(HttpServletResponse.SC_OK);
response.getWriter().print(" <script type=\"text/javascript\">\n" +
"parent.document.getElementById('clock').innerHTML = \"" + count.get() + "\";" +
"parent.document.getElementById('count').innerHTML = \"" + count.get() + "\";" +
"</script>");
}
}
}
SSE (我的方式)
WebSocketServer-sent eventsSSESSEHTTP
text/event-stream
SSEWebSocket- SSE 是基于HTTP协议的,它们不需要特殊的协议或服务器实现即可工作;
WebSocket需单独服务器来处理协议。
- SSE 单向通信,只能由服务端向客户端单向通信;webSocket全双工通信,即通信的双方可以同时发送和接受信息。
- SSE 实现简单开发成本低,无需引入其他组件;WebSocket传输数据需做二次解析,开发门槛高一些。
- SSE 默认支持断线重连;WebSocket则需要自己实现。
- SSE 只能传送文本消息,二进制数据需要经过编码后传送;WebSocket默认支持传送二进制数据。
SEEWebSockets自动重新连接事件ID发送任意事件<script>
let source = null;
let userId = 7777
if (window.EventSource) {
// 建立连接
source = new EventSource('http://localhost:7777/sse/sub/'+userId);
setMessageInnerHTML("连接用户=" + userId);
/**
* 连接一旦建立,就会触发open事件
* 另一种写法:source.onopen = function (event) {}
*/
source.addEventListener('open', function (e) {
setMessageInnerHTML("建立连接。。。");
}, false);
/**
* 客户端收到服务器发来的数据
* 另一种写法:source.onmessage = function (event) {}
*/
source.addEventListener('message', function (e) {
setMessageInnerHTML(e.data);
});
} else {
setMessageInnerHTML("你的浏览器不支持SSE");
}
</script>
SseEmittersseEmitterMapprivate static Map<String, SseEmitter> sseEmitterMap = new ConcurrentHashMap<>();
/**
* 创建连接
*
* @date: 2022/7/12 14:51
* @auther: 公众号:程序员小富
*/
public static SseEmitter connect(String userId) {
try {
// 设置超时时间,0表示不过期。默认30秒
SseEmitter sseEmitter = new SseEmitter(0L);
// 注册回调
sseEmitter.onCompletion(completionCallBack(userId));
sseEmitter.onError(errorCallBack(userId));
sseEmitter.onTimeout(timeoutCallBack(userId));
sseEmitterMap.put(userId, sseEmitter);
count.getAndIncrement();
return sseEmitter;
} catch (Exception e) {
log.info("创建新的sse连接异常,当前用户:{}", userId);
}
return null;
}
/**
* 给指定用户发送消息
*
* @date: 2022/7/12 14:51
* @auther: 公众号:程序员小富
*/
public static void sendMessage(String userId, String message) {
if (sseEmitterMap.containsKey(userId)) {
try {
sseEmitterMap.get(userId).send(message);
} catch (IOException e) {
log.error("用户[{}]推送异常:{}", userId, e.getMessage());
removeUser(userId);
}
}
}
IE
MQTT
MQTTpublishsubscribe轻量级Internet of Thingpublishersubscriber
TCPMQTTMQTTTCP/IPTCP/IPMQTTMQTTHTTP- 首先
HTTP协议它是一种同步协议,客户端请求后需要等待服务器的响应。而在物联网(IOT)环境中,设备会很受制于环境的影响,比如带宽低、网络延迟高、网络通信不稳定等,显然异步消息协议更为适合IOT应用程序。
HTTP是单向的,如果要获取消息客户端必须发起连接,而在物联网(IOT)应用程序中,设备或传感器往往都是客户端,这意味着它们无法被动地接收来自网络的命令。
- 通常需要将一条命令或者消息,发送到网络上的所有设备上。
HTTP要实现这样的功能不但很困难,而且成本极高。
websocketTCP
websocket<!-- 引入websocket -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>@ServerEndpointws://localhost:7777/webSocket/10086@Component
@Slf4j
@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>();
/**
* 公众号:程序员小富
* 链接成功调用的方法
*/
@OnOpen
public 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) {
}
}
/**
* 公众号:程序员小富
* 收到客户端消息后调用的方法
*/
@OnMessage
public 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();
}
}
}
}<script>
var ws = new WebSocket('ws://localhost:7777/webSocket/10086');
// 获取连接状态
console.log('ws连接状态:' + ws.readyState);
//监听是否连接成功
ws.onopen = function () {
console.log('ws连接状态:' + ws.readyState);
//连接成功则发送一个数据
ws.send('test1');
}
// 接听服务器发回的信息并处理展示
ws.onmessage = function (data) {
console.log('接收到来自服务器的消息:');
console.log(data);
//完成通信后关闭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: '/socket/publish?userId=10086&message=' + content,
type: 'GET',
data: { "id": "7777", "content": content },
success: function (data) {
console.log(data)
}
})
}
</script>

自定义推送

边栏推荐
- SocialFi 何以成就 Web3 去中心化社交未来
- 游戏窗口化的逆向分析
- 归一化与标准化
- onenote use
- rhce笔记1
- (1) Cloud computing technology learning - virtualized vSphere learning
- How to use Redis for distributed applications in Golang
- You are a first-class loser, you become a first-class winner
- DTSE Tech Talk丨Phase 2: 1 hour in-depth interpretation of SaaS application system design
- Visual Studio编辑器 2019:scanf函数返回值被忽略(C4996)报错及解决办法
猜你喜欢

【SOC FPGA】外设KEY点LED

Gorilla Mux 和 GORM 的使用方法

Login Module Debugging - Getting Started with Software Debugging

Jetpack Compose 到底优秀在哪里?| 开发者说·DTalk

Explore CSAPP Experiment 2-bomb lab-Section 1

为什么中年男人爱出轨?

@Bean注解详解
![[NCTF2019] Fake XML cookbook-1|XXE vulnerability|XXE information introduction](/img/29/92b9d52d17a203b8bdead3eb2c902e.png)
[NCTF2019] Fake XML cookbook-1|XXE vulnerability|XXE information introduction
![[flutter] What is MaterialApp and Material design](/img/72/d0845467b33b2291f47e7f54171088.jpg)
[flutter] What is MaterialApp and Material design

Visual Studio 集成Qt开发环境的一些注意事项
随机推荐
如何在分面中添加数学表达式标签?
Gvim order record
Mysql进阶优化篇01——四万字详解数据库性能分析工具(深入、全面、详细,收藏备用)
新技术要去做新价值
SocialFi 何以成就 Web3 去中心化社交未来
Nervegrowold d2l (7) kaggle housing forecast model, numerical stability and the initialization and activation function
【SOC FPGA】Peripheral KEY LED
LeetCode-283-移动零
onenote使用
You are a first-class loser, you become a first-class winner
win下搭建php环境的方法
onenote use
静态网页和动态网页的不同之处;该如何选择服务器呢
gvim命令记录
23. 请你谈谈关于IO同步、异步、阻塞、非阻塞的区别
游戏多开检测的几种实现方法及破解方法参考
How to connect redis in node.js?
疫情之下的裁员浪潮,7点建议帮你斩获心仪offer
新零售saas小程序如何探索数字化门店的破局之路?
arcpy tutorial