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

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

轻松实现服务器事件推送: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框架 实时通信

推荐文章

Shell 里给变量赋值为多行文本
2024-11-18 20:25:45 +0800 CST
基于Webman + Vue3中后台框架SaiAdmin
2024-11-19 09:47:53 +0800 CST
一文详解回调地狱
2024-11-19 05:05:31 +0800 CST
手机导航效果
2024-11-19 07:53:16 +0800 CST
php使用文件锁解决少量并发问题
2024-11-17 05:07:57 +0800 CST
thinkphp分页扩展
2024-11-18 10:18:09 +0800 CST
基于Flask实现后台权限管理系统
2024-11-19 09:53:09 +0800 CST
15 个 JavaScript 性能优化技巧
2024-11-19 07:52:10 +0800 CST
页面不存在404
2024-11-19 02:13:01 +0800 CST
最全面的 `history` 命令指南
2024-11-18 21:32:45 +0800 CST
防止 macOS 生成 .DS_Store 文件
2024-11-19 07:39:27 +0800 CST
Vue3中的Store模式有哪些改进?
2024-11-18 11:47:53 +0800 CST
HTML + CSS 实现微信钱包界面
2024-11-18 14:59:25 +0800 CST
7种Go语言生成唯一ID的实用方法
2024-11-19 05:22:50 +0800 CST
thinkphp swoole websocket 结合的demo
2024-11-18 10:18:17 +0800 CST
GROMACS:一个美轮美奂的C++库
2024-11-18 19:43:29 +0800 CST
20个超实用的CSS动画库
2024-11-18 07:23:12 +0800 CST
程序员茄子在线接单