消息推送的三种常见方式:轮询、SSE、WebSocket

摘要:本文介绍消息推送的三种常见方式轮询(定时请求,易增负担)与长轮询(阻塞请求至有数据 / 超时,减少请求)、SSE(HTTP 单向实时传输,纯文本、自动重连)、WebSocket(全双工通信,含客户端 API 与 Java 服务端 Endpoint 实现)。

1. 消息推送常见方式

轮询 长轮询 SSE Websocket

1.1 轮询方式

轮询

  • 工作机制 :浏览器按照指定的时间间隔,不断向服务器发送 HTTP 请求(如示例中的 GET/poll)。服务器收到请求后,会实时返回数据给浏览器。
  • 特点不管服务器有没有新数据,浏览器都会定时发请求**。这种方式可能导致请求过于频繁**,即便服务器无数据更新,也会产生大量请求,增加服务器和网络的负担,而且如果轮询间隔设置不合理,还可能出现数据更新的延迟(如图中 "延迟" 所示)。

长轮询

  • 工作机制 :浏览器发送 Ajax 请求(如示例中的 GET/lpoll)后,服务器接收到请求会 "阻塞" 该请求。也就是说,服务器不会立即返回响应,而是等待有数据更新或者请求超时的时候,才会把数据返回给浏览器。
  • 特点:相比轮询,长轮询减少了不必要的请求次数。只有当有数据变化或者超时,服务器才会响应,能在一定程度上降低服务器和网络的压力,也能更及时地获取数据更新(只要数据更新在超时前发生)。

简单来说,轮询是**"定时问"** ,长轮询是**"问了等,有了才回"**,二者在请求频率、资源消耗和数据实时性等方面存在差异,可根据实际场景选择使用。

1.2 SSE 技术

服务器发送事件(Server-Sent Events)是一种用于从服务器到客户端的 单向、实时 数据传输技术,基于 HTTP协议实现。

它有几个重要的特点:

  1. 单向通信:SSE 只支持服务器向客户端的单向通信,客户端不能向服务器发送数据。
  2. 文本格式:SSE 使用 纯文本格式 传输数据,使用 HTTP 响应的 text/event-stream MIME 类型。(数据流信息)
  3. 保持连接:SSE 通过保持一个持久的 HTTP 连接,实现服务器向客户端推送更新,而不需要客户端频繁轮询。
  4. 自动重连:如果连接中断,浏览器会自动尝试重新连接,确保数据流的连续性。

SSE 数据格式

SSE 数据流的格式非常简单,使用**event** 指定事件名称 ,用于区分不同类型的消息。每个事件使用 data 字段,作为消息主体 ,事件以两个换行符结束。还可以使用**id** 字段来标识事件,并且**retry**字段可以设置重新连接的时间间隔。

bash 复制代码
event: 事件名\n    // 可选,用于区分不同类型的消息
data: 消息内容\n    // 必选,消息主体(可多行,每行以 data: 开头)
id: 消息ID\n       // 可选,用于客户端记录最后接收的消息ID(重连时可通过 Last-Event-ID 头传递)
retry: 重连时间(毫秒)\n  // 可选,指定客户端重连间隔
\n  // 空行表示一条消息结束

示例格式如下:

bash 复制代码
data: Third message\n
id: 3\n
\n
retry: 10000\n
data: Fourth message\n
\n

1.3 WebSocket

WebSocket 是一种网络通信协议 ,它通过在单个、长期的连接上提供全双工(双向)的通信通道,来解决 HTTP 协议在实时通信方面的不足。

1.3.1 原理解析

这张图解析了 WebSocket 连接的建立过程及原理,可分为以下几个部分:

连接建立阶段(基于 HTTP 协议升级)

  1. 客户端请求 :客户端向服务器发送一个特殊的 HTTP 请求,请求中包含 Upgrade: websocket 等头信息,表明希望将连接从 HTTP 协议升级为 WebSocket 协议。下方 "请求数据" 框里展示了具体的请求内容,像 GET ws://localhost/chat HTTP/1.1(指定 WebSocket 连接的地址)、Connection: Upgrade(表示要升级连接)、Upgrade: websocket(明确升级到 WebSocket 协议)等。
  2. 服务器响应 :服务器收到请求后,返回 HTTP/1.1 101 Switching Protocols 响应,确认协议升级。下方 "响应数据" 框里呈现了响应的具体内容,包含 Upgrade: websocketConnection: Upgrade 等,表明已切换到 WebSocket 协议。

数据传输阶段(基于 WebSocket 协议)

当协议升级完成后,客户端和服务器就可以基于 WebSocket 协议进行全双工通信了,双方能相互主动发送数据(图中 "发送数据" 的双向箭头体现了这一点),不再受 HTTP 协议请求 - 响应模式的限制,适合实时性要求高的场景,比如在线聊天、实时数据推送等。

1.3.2 客户端API

1. WebSocket 对象创建

代码示例:let ws = new WebSocket(URL);

作用:在浏览器中创建一个 WebSocket 实例,用于和服务器建立 WebSocket 连接。

URL 说明:

  • 格式为 协议://ip地址/访问路径
  • 其中协议名称为 ws(若为加密连接则用 wss,类似 HTTPS),ip地址 是服务器的地址,访问路径 是 WebSocket 服务在服务器上的具体路径。

2. WebSocket 对象相关事件

这部分通过表格列出了 WebSocket 实例常用的事件及对应的事件处理程序和描述:

事件 事件处理程序 描述
open ws.onopen 当客户端与服务器成功建立 WebSocket 连接时触发该事件。
message ws.onmessage 当客户端接收到服务器发送的数据时触发该事件。
close ws.onclose 当 WebSocket 连接关闭时(无论是客户端主动关闭,还是服务器关闭,或者连接异常断开)触发该事件。

3. WebSocket 对象提供的方法

这部分介绍了 WebSocket 实例的方法:

方法名称 描述
send() 客户端通过调用 WebSocket 实例的 send() 方法,向服务器发送数据。
1.3.3 代码实现
javascript 复制代码
<script>
    let ws = new WebSocket("ws://localhost/chat");
    ws.onopen = function() {

    };
    ws.onmessage = function(evt) {
        // 通过 evt.data 可以获取服务器发送的数据
    };
    ws.onclose = function() {

    };
</script>

1. 创建 WebSocket 实例

javascript 复制代码
let ws = new WebSocket("ws://localhost/chat");
  • 使用 new WebSocket() 构造函数创建一个 WebSocket 连接对象。
  • 参数 "ws://localhost/chat" 是 WebSocket 服务器的地址,ws:// 表示使用 WebSocket 协议(如果是加密的 WebSocket 连接,使用 wss://),localhost 是本地服务器地址,/chat 是具体的 WebSocket 端点路径,用于标识要连接的服务。

2. 处理连接建立事件(onopen

javascript 复制代码
ws.onopen = function() {
    // 连接成功建立后执行的代码
};
  • onopen 是 WebSocket 对象的一个事件处理属性。
  • 当客户端与 WebSocket 服务器成功建立连接时,这个函数会被调用。通常可以在这里执行一些连接成功后的操作,比如向服务器发送初始化消息等。

3. 处理消息接收事件(onmessage

javascript 复制代码
ws.onmessage = function(evt) {
    // 通过 evt.data 可以获取服务器发送的数据
};
  • onmessage 事件在客户端接收到服务器发送的数据时触发。
  • 回调函数的参数 evt 包含了服务器发送的数据,通过 evt.data 可以获取到具体的数据内容,数据可以是字符串、Blob 或者 ArrayBuffer 等格式,这里通常需要根据实际情况对数据进行解析和处理,比如展示聊天消息、更新实时数据等。

4. 处理连接关闭事件(onclose

javascript 复制代码
ws.onclose = function() {
    // 连接关闭后执行的代码
};
  • onclose 事件在 WebSocket 连接关闭时触发,不管是客户端主动关闭还是服务器关闭连接,或者连接异常断开,这个函数都会被调用。可以在这里进行一些资源清理、提示用户连接已关闭或者尝试重新连接等操作。
1.3.4 服务端API

Java WebSocket 应用由一系列的 Endpoint 组成。Endpoint 是一个 Java 对象,代表 WebSocket 链接的一端,对于服务端,我们可以视为处理具体 WebSocket 消息的接口。

Endpoint 的定义方式

我们可以通过两种方式定义 Endpoint

  • 第一种是编程式,即继承类 javax.websocket.Endpoint 并实现其方法。
  • 第二种是注解式,即定义一个 POJO,并添加 @ServerEndpoint 相关注解。

Endpoint 的生命周期

Endpoint 实例在 WebSocket 握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在 Endpoint 接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:

方法 描述 注解
onOpen() 当开启一个新的会话时调用,该方法是客户端与服务端握手成功后调用的方法 @OnOpen
onClose() 当会话关闭时调用 @OnClose
onError() 当连接过程异常时调用 @OnError
1.3.5 服务端接收和推送数据
1.3.6 代码实现
java 复制代码
@ServerEndpoint("/chat")
@Component
public class ChatEndpoint {

    @OnOpen
    //连接建立时被调用
    public void onOpen(Session session, EndpointConfig config) {

    }

    @OnMessage
    //接收到客户端发送的数据时被调用
    public void onMessage(String message) {

    }

    @OnClose
    //连接关闭时被调用
    public void onClose(Session session) {

    }
}

大功告成!

相关推荐
天天开心a4 小时前
OSPF基础部分知识点
网络·笔记·学习·智能路由器·hcip
半路_出家ren4 小时前
路由策略实验配置
网络·rip·ospf·策略路由·路由策略
小薛博客5 小时前
22、Jenkins容器化部署Java应用
java·运维·jenkins
西贝爱学习5 小时前
如何在 IntelliJ IDEA 中进行全局替换某个字段(或文本)
java·ide·intellij-idea
l12345sy5 小时前
Day23_【机器学习—聚类算法—K-Means聚类 及评估指标SSE、SC、CH】
算法·机器学习·kmeans·聚类·sse·sc·ch
南部余额5 小时前
Spring 基于注解的自动化事务
java·spring·自动化
alf_cee5 小时前
通过Idea 阿里插件快速部署java jar包
java·ide·intellij-idea
小马哥编程5 小时前
如何在路由器上配置DHCP服务器?
服务器·网络·智能路由器
坚持每天敲代码5 小时前
【教程】IDEA中导入springboot-maven工程
java·maven·intellij-idea