一、 前言
在查看代码以后发现这些频繁的请求是因为我们的项目首页有一个待办任务数量和消息提醒数量的展示,所以之前的同事使用了定时器,每隔十秒钟发送一次请求到后端接口拿数据,这也就是我们常说的轮询做法。
1. 轮询的缺点
我们都知道轮询的缺点有几种:
资源浪费:
用户体验:
2. websocket的缺点
那么有没有替代轮询的做法呢? 聪明的同学肯定会第一时间想到用websocket
,但是在目前这个场景下我觉得使用websocket
是显得有些笨重。我从以下这几方面对比:
客户端实现:
适用场景:
实现复杂性:
浏览器支持:
服务器资源消耗:
二、 详细对比
对于这三者的详细区别,你可以参考下面我总结的表格:
以下是 WebSocket、轮询和 SSE 的对比表格:
特性 | WebSocket | 轮询Polling | Server-Sent Events (SSE) |
---|
定义 | 全双工通信协议,支持服务器和客户端之间的双向通信。 | 客户端定期向服务器发送请求以检查更新。 | 服务器向客户端推送数据的单向通信协议。 |
实时性 | 高,服务器可以主动推送数据。 | 低,依赖客户端定时请求。 | 高,服务器可以主动推送数据。 |
开销 | 相对较高,需要建立和维护持久连接。 | 较低,但频繁请求可能导致高网络和服务器开销。 | 相对较低,只需要一个HTTP连接,服务器推送数据。 |
浏览器支持 | 现代浏览器支持,需要额外的库来支持旧浏览器。 | 所有浏览器支持。 | 现代浏览器支持良好,旧浏览器可能需要polyfill。 |
实现复杂性 | 高,需要处理连接的建立、维护和关闭。 | 低,只需定期发送请求。 | 中等,只需要处理服务器推送的数据。 |
数据格式 | 支持二进制和文本数据。 | 通常为JSON或XML。 | 仅支持文本数据,通常为JSON。 |
控制流 | 客户端和服务器都可以控制消息发送。 | 客户端控制请求发送频率。 | 服务器完全控制数据推送。 |
安全性 | 需要wss://(WebSocket Secure)来保证安全。 | 需要https://来保证请求的安全。 | 需要SSE通过HTTPS提供,以保证数据传输的安全。 |
适用场景 | 需要双向交互的应用,如聊天室、实时游戏。 | 适用于更新频率不高的场景,如轮询邮箱。 | 适用于服务器到客户端的单向数据流,如股票价格更新。 |
跨域限制 | 默认不支持跨域,需要服务器配置CORS。 | 默认不支持跨域,需要服务器配置CORS。 | 默认不支持跨域,需要服务器配置CORS。 |
重连机制 | 客户端可以实现自动重连逻辑。 | 需要客户端实现重连逻辑。 | 客户端可以监听连接关闭并尝试重连。 |
服务器资源 | 较高,因为需要维护持久连接。 | 较低,但频繁的请求可能增加服务器负担。 | 较低,只需要维护一个HTTP连接。 |
这个表格概括了 WebSocket、轮询和 SSE 在不同特性上的主要对比点。每种技术都有其适用的场景和限制,选择合适的技术需要根据具体的应用需求来决定。
三、 SSE(Server-Sent Events)介绍
我们先来简单了解一下什么是Server-Sent Events
?
Server-Sent Events (SSE)
是一种允许服务器主动向客户端浏览器推送数据的技术。它基于 HTTP 协议
,但与传统的 HTTP 请求-响应模式不同,SSE 允许服务器在建立连接后,通过一个持久的连接不断地向客户端发送消息。
工作原理
建立连接:
服务器推送消息:
客户端接收消息:
连接管理:
著名的计算机科学家林纳斯·托瓦兹(Linus Torvalds) 曾经说过:talk is cheap ,show me your code
。
我们直接上代码看看效果:
java代码
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@RestController
@RequestMapping("platform/todo")
public class TodoSseController {
private final ExecutorService executor = Executors.newCachedThreadPool();
@GetMapping("/endpoint")
public SseEmitter refresh(HttpServletRequest request) {
final SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
executor.execute(() -> {
try {
while (true) { // 无限循环发送事件,直到连接关闭
// 发送待办数量更新
emitter.send(SseEmitter.event().data(5));
// 等待5秒
TimeUnit.SECONDS.sleep(5);
}
} catch (IOException e) {
emitter.completeWithError(e);
} catch (InterruptedException e) {
// 当前线程被中断,结束连接
Thread.currentThread().interrupt();
emitter.complete();
}
});
return emitter;
}
}
前端代码
beforeCreate() {
const eventSource = new EventSource('/platform/todo/endpoint');
eventSource.onmessage = (event) => {
console.log("evebt:",event)
};
eventSource.onerror = (error) => {
console.error('SSE error:', error);
eventSource.close();
};
this.$once('hook:beforeDestroy', () => {
if (eventSource) {
eventSource.close();
}
});
},
改造后的效果
可以看到,客户端只发送了一次http请求,后续所有的返回结果都可以在event.data
里面获取,先不谈性能,对于有强迫症的同学是不是一个很大改善呢?
总结
虽然 SSE
(Server-Sent Events)因其简单性和实时性在某些场景下提供了显著的优势,比如在需要服务器向客户端单向推送数据时,它能够以较低的开销维持一个轻量级的连接,但 SSE 也存在一些局限性。例如,它不支持二进制数据传输,这对于需要传输图像、视频或复杂数据结构的应用来说可能是一个限制。此外,SSE 只支持文本格式的数据流,这可能限制了其在某些数据传输场景下的应用。还有,SSE 的兼容性虽然在现代浏览器中较好,但在一些旧版浏览器中可能需要额外的 polyfill 或者降级方案。
考虑到这些优缺点,我们在选择数据通信策略时,应该基于项目的具体需求和上下文来做出决策。如果项目需要双向通信或者传输二进制数据,WebSocket 可能是更合适的选择。
如果项目的数据更新频率不高,或者只需要客户端偶尔查询服务器状态,传统的轮询可能就足够了。
而对于需要服务器频繁更新客户端数据的场景,SSE 提供了一种高效的解决方案。
总之,选择最合适的技术堆栈需要综合考虑项目的需求、资源限制、用户体验和未来的可维护性。