消息推送技术探索:轮询、SSE 与 WebSocket 的较量
·
在现代 Web 应用开发中,实现服务器向客户端实时推送消息的需求越来越普遍。无论是聊天应用、实时监控、股票行情还是协同编辑,都需要高效可靠的消息传递机制。本文将探讨三种主流的技术方案:轮询 (Polling)、服务器发送事件 (Server-Sent Events, SSE) 和 WebSocket,分析它们的工作原理、优缺点及适用场景。
1. 轮询 (Polling)
轮询是最基础、历史最悠久的实现“实时”通信的方法。它的核心思想很简单:客户端定期向服务器发送请求,询问是否有新数据可用。
轮询
- 工作机制:浏览器按照指定的时间间隔,不断向服务器发送 HTTP 请求(如示例中的
GET/poll)。服务器收到请求后,会实时返回数据给浏览器。 - 特点:不管服务器有没有新数据,浏览器都会定时发请求。这种方式可能导致请求过于频繁,即便服务器无数据更新,也会产生大量请求,增加服务器和网络的负担,而且如果轮询间隔设置不合理,还可能出现数据更新的延迟(如图中 “延迟” 所示)。
长轮询
- 工作机制:浏览器发送 Ajax 请求(如示例中的
GET/lpoll)后,服务器接收到请求会 “阻塞” 该请求。也就是说,服务器不会立即返回响应,而是等待有数据更新或者请求超时的时候,才会把数据返回给浏览器。 - 特点:相比轮询,长轮询减少了不必要的请求次数。只有当有数据变化或者超时,服务器才会响应,能在一定程度上降低服务器和网络的压力,也能更及时地获取数据更新(只要数据更新在超时前发生)。
缺点
- 效率低下: 大量请求可能是无效的(服务器无新数据),浪费网络带宽和服务器资源。
- 实时性差: 消息到达客户端有显著延迟,取决于轮询间隔。间隔短则资源消耗大,间隔长则实时性差。
- 服务器压力大: 在高并发场景下,大量的轮询请求会给服务器带来巨大压力。
适用场景
- 对实时性要求不高的简单应用。
- 老旧的浏览器环境或受限的设备。
- 作为其他更先进技术不可用时的备选方案。
2. 服务器发送事件 (Server-Sent Events, SSE)
SSE 是一种基于 HTTP 的轻量级协议,它允许服务器单向地向客户端推送数据流。它建立在 HTTP 之上,利用了 HTTP 的长连接特性。
工作原理
- 客户端使用
EventSourceAPI 向服务器发起一个特殊的 HTTP GET 请求。请求头中包含Accept: text/event-stream。 - 服务器收到请求后,保持连接打开(长连接),并设置响应头
Content-Type: text/event-stream。 - 当服务器有数据需要推送时,它通过这个持久化的连接发送遵循特定格式(如
data: <message>\n\n)的数据块。 - 客户端通过
EventSource对象的事件监听器(如onmessage)实时接收并处理这些数据。
服务器端示例:
import org.springframework.http.MediaType;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@RestController
@EnableScheduling
public class SseController {
@GetMapping(path = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter stream() {
SseEmitter emitter = new SseEmitter();
ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(() -> {
try {
String message = "Current time is " +
LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_TIME);
emitter.send(SseEmitter.event().data(message));
} catch (IOException e) {
scheduler.shutdown();
emitter.completeWithError(e);
}
}, 0, 1, TimeUnit.SECONDS);
return emitter;
}
}
客户端示例:
const eventSource = new EventSource('/stream');
eventSource.onmessage = function(event) {
console.log('New message:', event.data);
// 更新 UI
};
eventSource.onerror = function(error) {
console.error('EventSource error:', error);
eventSource.close();
};
优点
- 真正的服务器推送: 服务器可以在有新数据时立即发送,无需客户端请求。
- 基于 HTTP/HTTPS: 更容易通过防火墙和代理,通常不需要特殊配置。
- 轻量级: 协议简单,实现相对容易。
- 自动重连:
EventSourceAPI 内置了连接断开后的重连机制。 - 文本数据友好: 非常适合推送文本格式的更新(如 JSON)。
缺点
- 单向通信: 只能服务器向客户端推送,客户端无法通过同一个连接向服务器发送数据(仍需使用 AJAX 等)。
- 文本格式限制: 主要设计用于 UTF-8 文本数据,二进制数据支持有限或不方便。
- 最大并发连接数限制: 浏览器通常对每个源的并发 HTTP 连接数有限制(如 6 个),可能影响多个 SSE 流。
- 不支持旧浏览器: 较旧的浏览器(如 IE)不支持
EventSourceAPI。
适用场景
- 实时通知(新闻推送、社交媒体更新)。
- 监控仪表盘(实时显示服务器状态、日志)。
- 实时更新文本内容(股票价格、比分、聊天消息接收端)。
3. WebSocket
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。它在客户端和服务器之间建立持久连接后,双方可以随时互发数据,彻底打破了 HTTP 请求/响应模式的限制。
工作原理
- 握手 (Handshake): 客户端发起一个特殊的 HTTP GET 请求(包含
Upgrade: websocket等头部)。服务器如果支持 WebSocket,会返回101 Switching Protocols响应,完成协议升级。 - 建立连接: 握手成功后,底层的 TCP 连接保持打开,但通信协议从 HTTP 切换为 WebSocket 协议 (通常使用
ws://或wss://协议头)。 - 双向通信: 连接建立后,服务器和客户端可以随时、任意地向对方发送数据帧(可以是文本或二进制)。数据帧的开销很小。
- 关闭连接: 任何一方都可以发起关闭连接的握手。
客户端示例 :
const socket = new WebSocket('wss://your-server-endpoint');
socket.onopen = function(event) {
console.log('WebSocket connection opened');
socket.send('Hello Server!'); // 向服务器发送消息
};
socket.onmessage = function(event) {
console.log('Received from server:', event.data);
// 处理服务器推送的消息
};
socket.onerror = function(error) {
console.error('WebSocket error:', error);
};
socket.onclose = function(event) {
console.log('WebSocket connection closed', event.code, event.reason);
};
服务器端 :
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint("/websocket")
public class WebSocketServer {
@OnOpen
public void onOpen(Session session) throws IOException {
session.getBasicRemote().sendText("Welcome!"); // 主动推送消息
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
// 处理消息并回复
session.getBasicRemote().sendText("Echo: " + message);
}
@OnClose
public void onClose(Session session) {
// 连接关闭处理
}
@OnError
public void onError(Session session, Throwable error) {
error.printStackTrace();
}
}
优点
- 真正的全双工实时通信: 双向、低延迟的数据交换。
- 低开销: 建立连接后,数据帧头部很小,显著减少带宽消耗。
- 高性能: 避免了 HTTP 的开销(如每次请求的头信息)。
- 支持二进制数据: 高效传输文本和二进制数据。
- 标准协议: 现代浏览器广泛支持。
缺点
- 复杂性更高: 实现和管理 WebSocket 服务器相比 HTTP 服务更复杂。
- 连接管理: 需要处理连接状态、心跳保活、重连逻辑等。
- 代理和防火墙穿透: 某些严格的网络环境可能需要特殊配置才能允许 WebSocket 连接(
wss://通常更容易)。 - 状态保持: 持久连接意味着服务器需要管理更多状态(连接会话)。
适用场景
- 实时聊天应用。
- 多人在线游戏。
- 协同编辑工具。
- 高频金融交易平台。
- 需要实时双向数据流的任何应用。
总结对比
| 特性 | 轮询 (Polling) | 服务器发送事件 (SSE) | WebSocket |
|---|---|---|---|
| 通信方向 | 客户端主动拉取 | 服务器单向推送 | 服务器与客户端双向通信 |
| 协议基础 | HTTP | HTTP (长连接) | 独立的 WebSocket 协议 |
| 实时性 | 低 (延迟取决于间隔) | 高 | 非常高 |
| 效率/开销 | 高 (大量无效请求) | 中 (长连接,文本为主) | 低 (连接后开销小) |
| 数据格式 | 任意 | 文本为主 (UTF-8) | 文本与二进制 |
| 实现复杂度 | 低 | 中 | 高 |
| 浏览器支持 | 广泛 | 较广泛 (IE 不支持) | 广泛 (旧浏览器部分支持) |
| 适用场景 | 简单、低实时性需求 | 服务器向客户端单向推送 | 高实时性、双向交互应用 |
如何选择?
选择哪种消息推送技术取决于具体的应用需求:
- 需要简单、兼容性最好,且对实时性要求不高? 轮询可以作为起点或备选。
- 服务器需要主动向客户端推送文本更新,且不需要客户端频繁回传数据? SSE 是一个高效且相对简单的选择,尤其适用于通知、监控等场景。
- 需要高频率、低延迟、双向的实时交互? WebSocket 是最佳选择,尽管实现复杂度更高。
更多推荐



所有评论(0)