消息推送的三种常见方式:轮询、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协议实现。
它有几个重要的特点:
- 单向通信:SSE 只支持服务器向客户端的单向通信,客户端不能向服务器发送数据。
- 文本格式:SSE 使用 纯文本格式 传输数据,使用 HTTP 响应的
text/event-stream
MIME 类型。(数据流信息)- 保持连接:SSE 通过保持一个持久的 HTTP 连接,实现服务器向客户端推送更新,而不需要客户端频繁轮询。
- 自动重连:如果连接中断,浏览器会自动尝试重新连接,确保数据流的连续性。
SSE 数据格式
SSE 数据流的格式非常简单,使用 event
指定事件名称,用于区分不同类型的消息。每个事件使用 data
字段,作为消息主体,事件以两个换行符结束。还可以使用 id
字段来标识事件,并且 retry
字段可以设置重新连接的时间间隔。
event: 事件名\n // 可选,用于区分不同类型的消息
data: 消息内容\n // 必选,消息主体(可多行,每行以 data: 开头)
id: 消息ID\n // 可选,用于客户端记录最后接收的消息ID(重连时可通过 Last-Event-ID 头传递)
retry: 重连时间(毫秒)\n // 可选,指定客户端重连间隔
\n // 空行表示一条消息结束
示例格式如下:
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 协议升级)
- 客户端请求:客户端向服务器发送一个特殊的 HTTP 请求,请求中包含
Upgrade: websocket
等头信息,表明希望将连接从 HTTP 协议升级为 WebSocket 协议。下方 “请求数据” 框里展示了具体的请求内容,像GET ws://localhost/chat HTTP/1.1
(指定 WebSocket 连接的地址)、Connection: Upgrade
(表示要升级连接)、Upgrade: websocket
(明确升级到 WebSocket 协议)等。- 服务器响应:服务器收到请求后,返回
HTTP/1.1 101 Switching Protocols
响应,确认协议升级。下方 “响应数据” 框里呈现了响应的具体内容,包含Upgrade: websocket
、Connection: 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 代码实现
<script>let ws = new WebSocket("ws://localhost/chat");ws.onopen = function() {};ws.onmessage = function(evt) {// 通过 evt.data 可以获取服务器发送的数据};ws.onclose = function() {};
</script>
1. 创建 WebSocket 实例
let ws = new WebSocket("ws://localhost/chat");
- 使用
new WebSocket()
构造函数创建一个 WebSocket 连接对象。 - 参数
"ws://localhost/chat"
是 WebSocket 服务器的地址,ws://
表示使用 WebSocket 协议(如果是加密的 WebSocket 连接,使用wss://
),localhost
是本地服务器地址,/chat
是具体的 WebSocket 端点路径,用于标识要连接的服务。
2. 处理连接建立事件(onopen
)
ws.onopen = function() {// 连接成功建立后执行的代码
};
onopen
是 WebSocket 对象的一个事件处理属性。- 当客户端与 WebSocket 服务器成功建立连接时,这个函数会被调用。通常可以在这里执行一些连接成功后的操作,比如向服务器发送初始化消息等。
3. 处理消息接收事件(onmessage
)
ws.onmessage = function(evt) {// 通过 evt.data 可以获取服务器发送的数据
};
onmessage
事件在客户端接收到服务器发送的数据时触发。- 回调函数的参数
evt
包含了服务器发送的数据,通过evt.data
可以获取到具体的数据内容,数据可以是字符串、Blob 或者 ArrayBuffer 等格式,这里通常需要根据实际情况对数据进行解析和处理,比如展示聊天消息、更新实时数据等。
4. 处理连接关闭事件(onclose
)
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 代码实现
@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) {}
}
大功告成!