编程 服务器推送技术及其在Spring中的实现,特别是SseEmitter的功能与用途

2024-11-19 06:14:07 +0800 CST views 958

轻松实现服务器事件推送:Spring SseEmitter 详解

引言

服务器推送技术背景简介

服务器推送(Server Push)技术允许网站和应用在有新内容时主动向用户推送更新,而无需用户主动查询。与传统的"拉"模型不同,服务器推送采用"推"的方式将信息直接发送到客户端。其优点主要有:

  1. 用户体验更流畅:用户无需刷新页面即可获取最新内容,系统会在有新消息时自动推送给客户端。
  2. 更高效:服务器仅在有新内容时主动推送,减少了不必要的客户端请求。

常见的服务器推送技术包括:

  • 长轮询:客户端发起一个长时间的请求,服务器在有新内容时响应。虽然效率不高,但兼容性较好。
  • SSE (Server Sent Events):服务器持续向客户端推送事件,客户端只需监听一个事件源。兼容性一般。
  • WebSocket:基于TCP的双向通信,服务器和客户端建立持久连接,允许双向实时消息传输。兼容性较差但效率高。

Spring 的 SseEmitter 使用了 SSE 技术来实现服务器推送,与传统的 HTTP 长连接不同,它允许 Spring 服务主动向浏览器推送消息,显著提升用户体验。例如,在聊天应用中,只有在有新消息时才会主动推送,让用户感觉信息即时到达。

SseEmitter 的功能和用途

SseEmitter 的主要功能是允许服务器主动将信息推送给浏览器客户端。其主要特点包括:

  • 主动向单个客户端推送消息SseEmitter 能匹配唯一的客户端请求,并与之保持持久连接,通过该连接随时推送事件。
  • 推送重复的消息:允许服务器不停发送相同的消息,形成连续的事件流,客户端只需监听该事件流即可。
  • 支持延迟和定时推送:通过 @Scheduled 注解,服务器可以在指定时间推送延迟的事件。
  • 支持不同类型的事件:客户端可根据事件名称区分不同类型的事件,并作出相应响应。
  • 支持推送基本数据类型和 POJO 对象:服务器可以推送 Stringint 等基本类型,也可推送任意的 Java 对象。
  • 主动通知客户端关闭连接:通过调用 complete()error() 方法,服务器可以主动告知客户端连接已关闭。
  • 解耦服务器端和客户端:服务器端仅负责推送事件,与具体的客户端无关。

总的来说,SseEmitter 让服务器端能够主动推送信息给单个浏览器客户端,实现服务器推送的功能。这对于实时通信、实时消息推送非常有用,能够显著提高用户体验。

准备工作

引入 Maven 依赖

SseEmitter 包含在 spring-webmvc 包中,如果是 Spring Boot 项目,确保已引入如下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

使用 SseEmitter

以下是 Controller 接口的代码示例。首先同步返回一个建立的 SseEmitter 连接给客户端,然后在异步线程中进行数据推送。为了防止串流以及支持客户端主动停止推流,每次请求需携带唯一的客户端 ID。

@GetMapping(value = "test/{clientId}", produces = {MediaType.TEXT_EVENT_STREAM_VALUE})
@ApiOperation(value = "建立连接")
public SseEmitter test(@PathVariable("clientId") @ApiParam("客户端 id") String clientId) {
    final SseEmitter emitter = service.getConn(clientId);
    CompletableFuture.runAsync(() -> {
        try {
            service.send(clientId);
        } catch (Exception e) {
            throw new BusinessException("推送数据异常");
        }
    });
    return emitter;
}

@GetMapping("closeConn/{clientId}")
@ApiOperation(value = "关闭连接")
public Result<String> closeConn(@PathVariable("clientId") @ApiParam("客户端 id") String clientId) {
    service.closeConn(clientId);
    return Result.success("连接已关闭");
}

Service 层相关代码

private static final Map<String, SseEmitter> SSE_CACHE = new ConcurrentHashMap<>();

@Override
public SseEmitter getConn(@NotBlank String clientId) {
    final SseEmitter sseEmitter = SSE_CACHE.get(clientId);
    if (sseEmitter != null) {
        return sseEmitter;
    } else {
        final SseEmitter emitter = new SseEmitter(600_000L);
        emitter.onTimeout(() -> {
            logger.info("连接已超时,准备关闭,clientId = {}", clientId);
            SSE_CACHE.remove(clientId);
        });
        emitter.onCompletion(() -> {
            logger.info("连接已关闭,准备释放,clientId = {}", clientId);
            SSE_CACHE.remove(clientId);
        });
        emitter.onError(throwable -> {
            logger.error("连接异常,准备关闭,clientId = {}", clientId, throwable);
            SSE_CACHE.remove(clientId);
        });
        SSE_CACHE.put(clientId, emitter);
        return emitter;
    }
}

@Override
public void send(@NotBlank String clientId) throws IOException {
    final SseEmitter emitter = SSE_CACHE.get(clientId);
    emitter.send("此去经年", MediaType.APPLICATION_JSON);
    emitter.send("此去经年,应是良辰好景虚设");
    emitter.send("此去经年,应是良辰好景虚设,便纵有千种风情");
    emitter.send("此去经年,应是良辰好景虚设,便纵有千种风情,更与何人说");
    emitter.complete();
}

@Override
public void closeConn(@NotBlank String clientId) {
    final SseEmitter sseEmitter = SSE_CACHE.get(clientId);
    if (sseEmitter != null) {
        sseEmitter.complete();
    }
}

接口调试

如果在推送数据过程中客户端主动停止推送,可以直接调用关闭连接的接口。

注意事项

推送数据结束后,不要在 finally 块中调用 emitter.complete() 来关闭连接,否则可能触发 502 Bad Gateway 的异常。建议参考排查过程避免类似问题。

与 WebSocket 对比

SSEWebSocket 的主要区别在于:

  • 连接方式

    • SSE:客户端发送一个长连接请求,服务器通过 HTTP 响应推送事件。
    • WebSocket:建立双向通信,保持实时双向消息传输。
  • 传输效率

    • SSE:需频繁建立和关闭连接,效率不如 WebSocket。
    • WebSocket:保持长连接,效率更高。
  • 兼容性

    • SSE:原生支持的浏览器较少,需要 Polyfill。
    • WebSocket:现代浏览器全面支持。
  • 传输内容

    • SSE:只支持推送文本,不支持二进制数据。
    • WebSocket:支持推送文本和二进制数据。
  • 功能

    • SSE:仅支持服务器主动推送,客户端被动接收。
    • WebSocket:支持双向通信,客户端和服务器均可主动发送消息。
  • 使用场景

    • SSE:适用于服务器单向推送文本事件的场景。
    • WebSocket:适用于需要实时双向交互的场景。

总的来说,SSE 适用于服务器单向推送的场景,兼容性稍差但效率较高;而 WebSocket 更适合实时双向通信的场景,效率更高但兼容性要求较高。


复制全文 生成海报 服务器推送 Spring框架 实时通信

推荐文章

Web 端 Office 文件预览工具库
2024-11-18 22:19:16 +0800 CST
12个非常有用的JavaScript技巧
2024-11-19 05:36:14 +0800 CST
Nginx rewrite 的用法
2024-11-18 22:59:02 +0800 CST
一键配置本地yum源
2024-11-18 14:45:15 +0800 CST
php微信文章推广管理系统
2024-11-19 00:50:36 +0800 CST
Golang在整洁架构中优雅使用事务
2024-11-18 19:26:04 +0800 CST
Go 开发中的热加载指南
2024-11-18 23:01:27 +0800 CST
Rust 中的所有权机制
2024-11-18 20:54:50 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
淘宝npm镜像使用方法
2024-11-18 23:50:48 +0800 CST
Manticore Search:高性能的搜索引擎
2024-11-19 03:43:32 +0800 CST
Vue3中如何处理状态管理?
2024-11-17 07:13:45 +0800 CST
初学者的 Rust Web 开发指南
2024-11-18 10:51:35 +0800 CST
程序员茄子在线接单