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

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

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

推荐文章

全新 Nginx 在线管理平台
2024-11-19 04:18:33 +0800 CST
PHP openssl 生成公私钥匙
2024-11-17 05:00:37 +0800 CST
Vue3中的v-for指令有什么新特性?
2024-11-18 12:34:09 +0800 CST
使用Rust进行跨平台GUI开发
2024-11-18 20:51:20 +0800 CST
PHP 允许跨域的终极解决办法
2024-11-19 08:12:52 +0800 CST
一个数字时钟的HTML
2024-11-19 07:46:53 +0800 CST
Vue 中如何处理父子组件通信?
2024-11-17 04:35:13 +0800 CST
ElasticSearch简介与安装指南
2024-11-19 02:17:38 +0800 CST
Nginx 实操指南:从入门到精通
2024-11-19 04:16:19 +0800 CST
介绍 Vue 3 中的新的 `emits` 选项
2024-11-17 04:45:50 +0800 CST
使用Vue 3和Axios进行API数据交互
2024-11-18 22:31:21 +0800 CST
html一份退出酒场的告知书
2024-11-18 18:14:45 +0800 CST
nginx反向代理
2024-11-18 20:44:14 +0800 CST
api远程把word文件转换为pdf
2024-11-19 03:48:33 +0800 CST
浏览器自动播放策略
2024-11-19 08:54:41 +0800 CST
Go 语言实现 API 限流的最佳实践
2024-11-19 01:51:21 +0800 CST
Nginx负载均衡详解
2024-11-17 07:43:48 +0800 CST
程序员茄子在线接单