[Java] 服务端消息推送汇总

news2024/11/26 20:20:40

前言:当构建实时消息推送功能时,选择适合的方案对于开发高效的实时应用至关重要。消息的推送无非就推、拉两种数据模型。本文将介绍四种常见的消息实时推送方案:短轮询(拉)、长轮训(拉)、SSE(Server-Sent Events)(推)和WebSocket(推),并以Spring Boot作为技术底座,展示如何在Java全栈开发中实现这些功能。


文章目录

    • 1. 短轮询(Short Polling)
      • 什么是短轮询?
      • 短轮询的实现
      • 短轮询的特点与限制
    • 2. 长轮询(Long Polling)
      • 什么是长轮询?
      • 长轮询的实现
      • 长轮询的特点与限制
    • 3. SSE(Server-Sent Events)
      • 什么是SSE?
      • SSE的实现
      • SSE的特点与限制
    • 4. WebSocket
      • 什么是WebSocket?
      • WebSocket的实现
        • 1. 创建一个WebSocket处理器:
        • 2. 配置WebSocket端点:
        • 3. 前端实现
      • WebSocket的特点与限制
  • 总结
  • 注意


1. 短轮询(Short Polling)

什么是短轮询?

短轮询是一种简单的实时消息推送方案,其中客户端通过定期向服务器发送请求来获取最新的消息。服务器在接收到请求后立即响应,无论是否有新消息。如果服务器没有新消息可用,客户端将再次发送请求。

短轮询的实现

在Spring Boot中,可以通过HTTP接口和定时任务来实现短轮询。下面是一个简单的示例:

// -- 后端接口
@GetMapping("/short")
public String  getShort() {
    long l = System.currentTimeMillis();
    if((l&1) == 1) {
        return "ok";
    }
    return "fail";
}


// --- 前端页面
@org.springframework.stereotype.Controller
public class Controller {

    @GetMapping("/s")
    public String s() {
        return "s";
    }
}

// -- s.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>短轮询</title>
</head>
<body>
<p>msg=<span id="message"></span></p>
<script>
    function pollMessage() {
// 发送轮询请求
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "/short", true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                if (xhr.status === 200) {
                    document.getElementById("message").innerHTML = xhr.responseText;
                }
            }
        };
        xhr.send();
    }
    setInterval(()=>{
        pollMessage()
    }, 1000)

</script>
</body>
</html>


在上述示例中,getShort()方法用于返回消息,而s方法用于渲染s.html。客户端可以定期调用getShort()接口来获取最新的消息。

短轮询的特点与限制

短轮询的实现简单,但存在一些特点和限制:

  • 高延迟: 客户端需要定期发送请求,无论是否有新消息。这会导致一定的延迟,特别是在消息更新较慢的情况下。
  • 高网络负载: 客户端需要频繁发送请求,即使消息没有更新。这会增加服务器和网络的负载。
  • 实时性差: 由于需要等待下一次轮询才能获取新消息,短轮询的实时性相对较差。

2. 长轮询(Long Polling)

什么是长轮询?

长轮询是改进的轮询方法,它在没有新消息时会保持请求挂起,直到有新消息到达或超时。相比于短轮询,长轮询可以更快地获取新消息,减少了不必要的请求。

长轮询的实现

在Spring Boot中,可以使用异步请求和定时任务来实现长轮询。下面是一个简单的示例:

// -- 请求接口

/**
 * 长轮询
 * @return
 */
@GetMapping("/long")
public DeferredResult<String> getLong() {
    DeferredResult<String> deferredResult = new DeferredResult<>();

    if (latestMessage != null) {
        deferredResult.setResult(latestMessage);
    } else {
        // 使用定时任务设置超时时间
        TimerTask timeoutTask = new TimerTask() {
            @Override
            public void run() {
                deferredResult.setResult(null);
            }
        };
        Timer timer = new Timer();
        timer.schedule(timeoutTask, 5000); // 设置超时时间为5秒

        // 设置回调函数,在消息到达时触发
        deferredResult.onTimeout(() -> {
            timer.cancel();
            deferredResult.setResult(null);
        });

        deferredResult.onCompletion(timer::cancel);
    }

    return deferredResult;
}

/**
 * 设置消息
 * @param message
 */
@PostMapping("/send-message")
public void sendMessage(@RequestBody String message) {
    latestMessage = message;
}


// -- 前端请求

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>长轮询</title>
</head>
<body>
<p>msg=<span id="message"></span></p>
<p>请求次数:<span id="cnt"></span></p>
<script>
    var cnt = 0
    function pollMessage() {
// 发送轮询请求
        const xhr = new XMLHttpRequest();
        xhr.open("GET", "/long", true);
        xhr.onreadystatechange = function () {
            if (xhr.readyState === XMLHttpRequest.DONE) {
                if (xhr.status === 200) {
                    document.getElementById("message").innerHTML = xhr.responseText;
                }
            }
        };
        xhr.send();
    }

    setInterval(()=>{
        ++cnt;
        document.getElementById('cnt').innerHTML = cnt.toString()
        pollMessage()
    }, 5000)

</script>
</body>
</html>



在上述示例中,getLong()方法返回一个DeferredResult对象,它会在有新消息到达时触发回调函数。如果在超时时间内没有新消息到达,DeferredResult对象将返回null

长轮询的特点与限制

长轮询具有以下特点与限制:

  • 减少请求次数: 长轮询可以更快地获取新消息,相比于短轮询,可以减少不必要的请求次数。
  • 减少网络负载: 当没有新消息时,长轮询会保持请求挂起,减少了频繁的请求,从而减轻了服务器和网络的负载。
  • 相对实时性提升: 长轮询可以更快地获取新消息,相比于短轮询,实时性有所提升。然而,仍然需要等待下一次轮询才能获取新消息。

3. SSE(Server-Sent Events)

在这里插入图片描述

什么是SSE?

当使用Server-Sent Events(SSE)时,客户端(通常是浏览器)与服务器之间建立一种持久的连接,使服务器能够主动向客户端发送数据。这种单向的、服务器主动推送数据的通信模式使得实时更新的数据能够被实时地传送到客户端,而无需客户端进行轮询请求。

SSE的工作原理如下:

  1. 建立连接:客户端通过使用EventSource对象在浏览器中创建一个与服务器的连接。客户端向服务器发送一个HTTP请求,请求的头部包含Accept: text/event-stream,以表明客户端希望接收SSE数据。服务器响应这个请求,并建立一个持久的HTTP连接。
  2. 保持连接:服务器保持与客户端的连接打开状态,不断发送数据。这个连接是单向的,只允许服务器向客户端发送数据,客户端不能向服务器发送数据。
  3. 服务器发送事件:服务器使用Content-Type: text/event-stream标头来指示响应是SSE数据流。服务器将数据封装在特定的SSE格式中,每个事件都以data:开头,后面是实际的数据内容,以及可选的其他字段,如event:id:。服务器发送的数据可以是任何文本格式,通常是JSON。
  4. 客户端接收事件:客户端通过EventSource对象监听服务器发送的事件。当服务器发送事件时,EventSource对象会触发相应的事件处理程序,开发人员可以在处理程序中获取到事件数据并进行相应的操作。常见的事件是message事件,表示接收到新的消息。
  5. 断开连接:当客户端不再需要接收服务器的事件时,可以关闭连接。客户端可以调用EventSource对象的close()方法来显式关闭连接,或者浏览器在页面卸载时会自动关闭连接。

SSE的实现

在Spring Boot中,可以使用SseEmitter类来实现SSE。下面是一个简单的示例:

@RestController
public class SSEController {
    private SseEmitter sseEmitter;

    @GetMapping("/subscribe")
    public SseEmitter subscribe() {
        sseEmitter = new SseEmitter();
        return sseEmitter;
    }

    @PostMapping("/send-message")
    public void sendMessage(@RequestBody String message) {
        try {
            if (sseEmitter != null) {
                sseEmitter.send(SseEmitter.event().data(message));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}


// - s.html

<!DOCTYPE html>
<html>
<head>
    <title>SSE Demo</title>
</head>
<body>
<h1>SSE Demo</h1>
<div id="message-container"></div>

<script>
    // 创建一个EventSource对象,指定SSE的服务端端点
    var eventSource = new EventSource('/subscribe');
    console.log("eventSource=", eventSource)
    // 监听message事件,接收从服务端发送的消息
    eventSource.addEventListener('message', function(event) {
        var message = event.data;
        console.log("message=", message)
        var messageContainer = document.getElementById('message-container');
        messageContainer.innerHTML += '<p>' + message + '</p>';
    });
</script>
</body>
</html>

在上述示例中,客户端可以通过访问/subscribe接口来订阅SSE事件,服务器会返回一个SseEmitter对象。当有新消息到达时,调用SseEmitter对象的send()方法发送消息。

在这里插入图片描述

SSE的特点与限制

SSE具有以下特点与限制:

  • 实时性较好: SSE使用了持久连接,可以实现比短轮询和长轮询更好的实时性。
  • 单向通信: SSE是单向的,只允许服务器向客户端推送消息,客户端无法向服务器发送消息。
  • 不适用于低版本浏览器: SSE是HTML5的一部分,不支持低版本的浏览器。在使用SSE时,需要确保客户端浏览器的兼容性。

4. WebSocket

在这里插入图片描述

什么是WebSocket?

WebSocket是一种双向通信协议,允许在单个持久连接上进行全双工通信。与之前介绍的方案不同,WebSocket提供了双向通信的能力,可以实现实时的双向数据传输。

WebSocket的实现

在Spring Boot中,可以使用Spring WebSocket模块来实现WebSocket功能。下面是一个简单的示例:

1. 创建一个WebSocket处理器:
@Component
public class WebSocketHandler extends TextWebSocketHandler {
    private List<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        for (WebSocketSession webSocketSession : sessions) {
            webSocketSession.sendMessage(message);
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        sessions.remove(session);
    }
}
2. 配置WebSocket端点:
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Autowired
    private WebSocketHandler webSocketHandler;

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(webSocketHandler, "/websocket").setAllowedOrigins("*");
    }
}

在上述示例中,WebSocketHandler处理器负责处理WebSocket连接、消息传递和连接关闭等事件。WebSocketConfig类用于配置WebSocket端点。

3. 前端实现
<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Demo</title>
</head>
<body>
<h1>WebSocket Demo</h1>
<div id="message-container"></div>

<script>
    // 创建WebSocket对象,并指定服务器的URL
    var socket = new WebSocket('ws://localhost:8080/websocket');

    // 监听WebSocket的连接事件
    socket.onopen = function(event) {
        console.log('WebSocket connected');
    };

    // 监听WebSocket的消息事件
    socket.onmessage = function(event) {
        var message = event.data;
        var messageContainer = document.getElementById('message-container');
        messageContainer.innerHTML += '<p>' + message + '</p>';
    };

    // 监听WebSocket的关闭事件
    socket.onclose = function(event) {
        console.log('WebSocket closed');
    };

    // 发送消息到服务器
    function sendMessage() {
        var messageInput = document.getElementById('message-input');
        var message = messageInput.value;
        socket.send(message);
        messageInput.value = '';
    }
</script>

<input type="text" id="message-input" placeholder="Enter message">
<button onclick="sendMessage()">Send</button>
</body>
</html>

在这里插入图片描述

WebSocket的特点与限制

WebSocket具有以下特点与限制:

  • 实时性最佳: WebSocket 提供了真正的双向通信,可以实现实时的双向数据传输,具有最佳的实时性。
  • 低延迟: 与轮询和长轮询相比,WebSocket 使用单个持久连接,减少了连接建立和断开的开销,从而降低了延迟。
  • 双向通信: WebSocket 允许服务器与客户端之间进行双向通信,服务器可以主动向客户端发送消息,同时客户端也可以向服务器发送消息。
  • 较高的网络负载: WebSocket 使用长连接,会占用一定的网络资源。在大规模并发场景下,需要注意服务器的负载情况。
  • 浏览器支持: 大多数现代浏览器都支持 WebSocket,但需要注意在开发过程中考虑不同浏览器的兼容性。

总结

本文介绍了四种常见的消息实时推送方案:短轮询、长轮询、SSE WebSocket,并以 Spring Boot 作为技术底座,展示了如何在 Java 全栈开发中实现这些功能。

  • 短轮询是一种简单的实时消息推送方案,但存在高延迟、高网络负载和实时性差的限制。
  • 长轮询通过保持请求挂起来减少不必要的请求次数,提高了实时性,但仍需要轮询才能获取新消息。
  • SSE 使用持久连接实现单向实时消息推送,具有较好的实时性,但只支持服务器向客户端的单向通信。
  • WebSocket 提供了真正的双向通信,具有最佳的实时性和低延迟,但需要注意较高的网络负载和浏览器兼容性。

选择合适的消息实时推送方案取决于具体的需求和场景。根据应用程序的要求和预期的用户体验,开发人员可以选择适当的方案来实现实时消息推送功能。

注意

以上实现均属于demo 级别,为了简单演示,将所有的服务保证措施都删除了,所以存在包括但不限于以下缺点

比如

  • sse
  1. 没有做会话管理
  2. 明文传输
  • WebSocket
  1. 客户端连接的区分,目前的实现属于消息广播。

  2. 连接的可靠性保证:心跳检测以及自动重连等。

  3. 消息的明文传输

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1070572.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

详解C语言指针(二)

文章目录 1. 字符指针2. 指针数组3. 数组指针3.1 什么是数组指针&#xff1f;3.2 &数组名 VS 数组名 4. 数组参数4.1 一维数组传参4.2 二维数组传参 5. 函数指针6. 函数指针数组7. 指向函数指针数组的指针8. 回调函数 1. 字符指针 字符指针是指针类型的变量&#xff0c;其…

文本自动输入/删除的加载动画效果

效果展示 CSS 知识点 绕矩形四周跑的光柱动画实现animation 属性的 steps 属性值运用 页面基础结构实现 <div class"loader"><!-- span 标签是围绕矩形四周的光柱 --><span></span><span></span><span></span>&l…

Git 学习笔记 | 使用码云

Git 学习笔记 | 使用码云 Git 学习笔记 | 使用码云注册登录码云&#xff0c;完善个人信息设置本机绑定SSH公钥&#xff0c;实现免密码登录创建远程仓库 Git 学习笔记 | 使用码云 注册登录码云&#xff0c;完善个人信息 网址&#xff1a;https://gitee.com/ 可以使用微信&…

SpringBoot结合dev-tool 实现IDEA项目热部署

什么是热部署&#xff1f; 应用正在运行的时候升级功能, 不需要重新启动应用对于Java应用程序来说, 热部署就是在运行时更新Java类文件 通俗的来讲&#xff0c;应用在运行状态下&#xff0c;修改项目源码后&#xff0c;不用重启应用&#xff0c;会把编译的内容部署到服务器上…

【Acwing1010】拦截导弹(LIS+贪心)题解

题目描述 思路分析 本题有两问&#xff0c;第一问直接用lis的模板即可&#xff0c;下面重点看第二问 思路是贪心&#xff1a; 贪心流程&#xff1a; 从前往后扫描每一个数&#xff0c;对于每个数&#xff1a; 情况一&#xff1a;如果现有的子序列的结尾都小于当前的数&…

stm32的GPIO寄存器操作以及GPIO外部中断,串口中断

一、学习参考资料 &#xff08;1&#xff09;正点原子的寄存器源码。 &#xff08;2&#xff09;STM32F103最小系统板开发指南-寄存器版本_V1.1&#xff08;正点&#xff09; &#xff08;3&#xff09;STM32F103最小系统板开发指南-库函数版本_V1.1&#xff08;正点&a…

【重拾C语言】七、指针(一)指针与变量、指针操作、指向指针的指针

目录 前言 七、指针 7.1 指针与变量 7.1.1 指针类型和指针变量 7.1.2 指针所指变量 7.1.3 空指针、无效指针 7.2 指针操作 7.2.1 指针的算术运算 7.2.2 指针的比较 7.2.3 指针的递增和递减 7.3 指向指针的指针 前言 指针是C语言中一个重要的概念正确灵活运用指针 可…

单元测试该怎么写

单元测试对于开发人员来说很熟悉&#xff0c;各种语言都提供了单元测试的框架&#xff0c;用于自动化执行单元测试并生成测试报告。它通常提供了一组API和工具&#xff0c;使开发人员能够编写和运行测试用例&#xff0c;比较预期行为和实际行为之间的差异&#xff0c;并准确地识…

Android Studio新建项目缓慢解决方案

关于Android Studio2022新建项目时下载依赖慢的解决方案 起因解决方案gradle下载慢解决方案kotlin依赖下载慢解决方案 结尾 起因 新建Android Studio项目时&#xff0c;常会因为网络问题导致部分依赖下载缓慢&#xff0c;其中gradle和kotlin最拖慢进度。 解决方案 gradle下载…

Spring源码解析——IOC属性填充

正文 doCreateBean() 主要用于完成 bean 的创建和初始化工作&#xff0c;我们可以将其分为四个过程&#xff1a; 最全面的Java面试网站 createBeanInstance() 实例化 beanpopulateBean() 属性填充循环依赖的处理initializeBean() 初始化 bean 第一个过程实例化 bean在前面一篇…

四位十进制数字频率计VHDL,仿真视频、代码

名称&#xff1a;四位十进制数字频率计VHDL&#xff0c;quartus仿真 软件&#xff1a;Quartus 语言&#xff1a;VHDL 代码功能&#xff1a; 使用直接测频法测量信号频率&#xff0c;测频范围为1~9999Hz&#xff0c;具有超量程报警功能 演示视频&#xff1a;四位十进制数字频…

5分钟理解什么是卷积的特征提取

大家好啊&#xff0c;我是董董灿。 卷积算法之所以重要&#xff0c;关键在于其提取特征的能力。 5分钟入门卷积算法中提到&#xff0c;卷积模仿的就是人眼识图的过程&#xff0c;以“感受野”的视角去扫描图片&#xff0c;从而获取不同区域的图片信息。 在这一过程中&#x…

Scratch3.0下载

通俗易懂&#xff0c;直接上链接 链接&#xff1a;https://pan.baidu.com/s/1n-QFEQWT8im8BHQu1wIjtg?pwd1016 提取码&#xff1a;1016

高级IO(Linux)

高级IO 五种IO模型高级IO重要概念同步通信 vs 异步通信阻塞 vs 非阻塞 非阻塞IOfcntl实现函数SetNoBlock轮询方式读取标准输入 I/O多路转接之select初识selectselect函数原型参数解释参数timeout取值关于fd_set结构关于timeval结构函数返回值三级目录 理解select执行过程socket…

多功能频率计周期/脉宽/占空比/频率测量verilog,视频/代码

名称&#xff1a;多功能频率计周期、脉宽、占空比、频率测量verilog 软件&#xff1a;Quartus 语言&#xff1a;Verilog 代码功能&#xff1a; 多功能频率计&#xff0c;可测量信号的周期、脉冲宽度、占空比、频率&#xff0c;语言为verilog&#xff0c;quartus软件设计仿真…

B (1089) : DS单链表--合并

Description 假定两个单链表是递增有序&#xff0c;定义并实现以下函数&#xff0c;完成两个单链表的合并&#xff0c;继续保持递增有序 int LL_merge(ListNode *La, ListNode *Lb) Input 第1行先输入n表示有n个数据&#xff0c;接着输入n个数据 第2行先输入m表示有M个数据…

扭线机控制

扭线机属于线缆加工设备&#xff0c;线缆加工设备种类非常多。有用于网线绞合的单绞&#xff0c;双绞机等&#xff0c;有关单绞机相关算法介绍&#xff0c;大家可以查看专栏相关文章&#xff0c;有详细介绍&#xff0c;常用链接如下&#xff1a; 线缆行业单绞机控制算法&#…

MySQL命令行中文乱码问题

MySQL命令行中文乱码问题&#xff1a; 命令行界面默认字符集是gbk&#xff0c;若字符集不匹配会中文乱码或无法插入中文。 解决办法&#xff1a;执行set names gbk; 验证&#xff1a; 执行命令show variables like ‘char%’;查看默认字符集。 创建数据库设置字符集utf8&…

Nginx详细学习记录

1. Nginx概述 Nginx是一个轻量级的高性能HTTP反向代理服务器&#xff0c;同时它也是一个通用类型的代理服务器&#xff0c;支持绝大部分协议&#xff0c;如TCP、UDP、SMTP、HTTPS等。 1.1 Nginx基础架构 Nginx默认采用多进程工作方式&#xff0c;Nginx启动后&#xff0c;会运行…

多线程锁-synchronized字节码分析

从字节码角度分析synchronized实现 javap -c(v附加信息) ***.class 文件反编译 synchronized同步代码块 >>>实现使用的是monitorenter和monitorexit指令 synchronized普通同步方法 >>>调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置&#xf…