引言

服务器推送技能背景简介

服务器推送(Server Push)技能答应网站和运用在有新内容可用时自意向用户推送更新,而不需求用户自动去查询。与传统的”拉”模型不同,服务器推送选用”推”的方法自动把信息发给客户端。服务器推送的优点有两个:

  1. 用户体验更流通。用户不需求一向去刷新页面来获取最新内容,系统会在有新的音讯出现时自动推送给客户端。

  2. 更高效。服务器只在有真实有用的内容时才自动推送,节省了很多不必要的客户端恳求。常见的服务器推送技能包括:

    • 长轮询:客户端向服务器发起一个长时刻的恳求,一向坚持翻开,直到服务器有新内容推送。功率不高但兼容性好。
    • SSE(Server Sent Events):服务器能够在需求时一向向客户端推送事情,客户端只需求监听一个事情源。兼容性一般。
    • WebSocket:基于TCP的双向通讯,服务器和客户端树立耐久衔接,答应双向实时音讯传输。兼容性差但功率高。

Spring SseEmitter便是运用SSE技能完成服务器推送。与传统的Http长衔接不同,它答应Spring服务能自意向浏览器推送音讯。这能够明显进步用户体验。比如在谈天运用中,只有在有新音讯时才自动推送,让用户感觉及时接收到信息。

SseEmitter 的功用和用途

SseEmitter 的首要功用便是答应服务器能自动将信息推送给浏览器客户端。它完成了服务器推送功用。它的首要功用和用途有以下几个:

  1. 能自意向单个客户端推送音讯。SseEmitter能匹配仅有的客户端恳求,并与该客户端坚持耐久衔接。经过此衔接,服务器能够随时将事情推送给这个客户端。
  2. 能推送重复的音讯。SseEmitter答应服务器不断发送相同的音讯给客户端,构成一个连续的事情流。客户端只需求监听这个事情流即可。
  3. 支撑推迟和定时推送。经过@Scheduled注解,服务器能够在指定时刻推送指定推迟的事情。
  4. 支撑推送不同类型的事情。客户端经过事情的名称能区别不同类型的事情,并作出不同的呼应。
  5. 支撑推送基本数据类型和POJO目标。服务器能够推送String、int等基本类型,也能够推送任意的Java目标。
  6. 能自动告诉客户端封闭。经过调用complete()或error()方法,服务器能够自动奉告客户端衔接已封闭。
  7. 解耦服务器端和客户端。服务器端仅担任推送事情,与具体的客户端无关。

总的来说,SseEmitter的作用便是让服务器端能自动将信息推送给单个浏览器客户端,完成服务器推送的功用。它解耦了服务器端和客户端,给予服务器端主权自动推送事情的能力。这对实时通讯、实时音讯推送十分有用,能明显进步用户体验。

准备工作

引进maven依靠

SseEmitter 包含在 spring-webmvc 包中,假如是 spring boot 项目,确定现已引进了如下依靠即可

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

运用 SseEmitter

  1. 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("衔接已封闭");  
}
  1. Sevice 层相关代码如下
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 {  
        // 设置衔接超时时刻,需求配合装备项 spring.mvc.async.request-timeout: 600000 一起运用  
        final SseEmitter emitter = new SseEmitter(600_000L);  
        // 注册超时回调,超时后触发
        emitter.onTimeout(() -> {  
            logger.info("衔接已超时,正准备封闭,clientId = {}", clientId);  
            SSE_CACHE.remove(clientId);  
        });  
        // 注册完成回调,调用 emitter.complete() 触发
        emitter.onCompletion(() -> {  
            logger.info("衔接已封闭,正准备开释,clientId = {}", clientId);  
            SSE_CACHE.remove(clientId);  
            logger.info("衔接已开释,clientId = {}", clientId);  
        });  
        // 注册反常回调,调用 emitter.completeWithError() 触发
        emitter.onError(throwable -> {  
            logger.error("衔接已反常,正准备封闭,clientId = {}", clientId, throwable);  
            SSE_CACHE.remove(clientId);  
        });  
        SSE_CACHE.put(clientId, emitter);  
        return emitter;  
    }  
}
/**  
* 模拟类似于 chatGPT 的流式推送答复  
*  
* @param clientId 客户端 id  
* @throws IOException 反常  
*/  
@Override  
public void send(@NotBlank String clientId) throws IOException {  
    final SseEmitter emitter = SSE_CACHE.get(clientId);  
    // 推流内容到客户端  
    emitter.send("此去经年", org.springframework.http.MediaType.APPLICATION_JSON);  
    emitter.send("此去经年,应是良辰好景虚设");  
    emitter.send("此去经年,应是良辰好景虚设,便纵有千种风情");  
    emitter.send("此去经年,应是良辰好景虚设,便纵有千种风情,更与何人说");  
    // 完毕推流  
    emitter.complete();  
}
@Override  
public void closeDialogueConn(@NotBlank String clientId) {  
    final SseEmitter sseEmitter = SSE_CACHE.get(clientId);   
    if (sseEmitter != null) {  
        sseEmitter.complete();  
    }  
}
  1. 接口调试

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

假如在推送数据过程中由客户端自动中止推送数据,能够直接调用封闭衔接的接口。

注意事项

  1. 推送数据完毕后,不要在 finally 块中调用 emitter.complete() 来封闭衔接,否则会触发一个很诡异的BUG,假如此时在很短的时刻内恳求别的接口,可能会收到一个502 bad Gateway 的反常信息,原因便是和这个帖子 记一次springboot运用偶发502过错的排查过程_帅帅兔子的博客-CSDN博客 差不多。

与 WebSocket 比照

SSE(SseEmitter)与WebSocket的首要区别:

  1. 树立衔接的方法不同:
  • SSE:客户端发送一个长衔接恳求,然后服务端将事情经过 HTTP 呼应推送给客户端。
  • WebSocket:选用双工通讯,客户端和服务器树立实时的双向通讯信道。
  1. 传输功率不同:
  • SSE:需求经常树立和封闭衔接,功率不如 WebSocket。但支撑 HTTP 缓存。
  • WebSocket:树立后坚持衔接不断,功率高于SSE。
  1. 兼容性不同:
  • SSE:原生支撑的浏览器相对较少。需求Polyfill。
  • WebSocket:现代浏览器基本全面支撑。
  1. 传输内容不同:
  • SSE:只答应推送文本,不支撑传输二进制数据。
  • WebSocket:支撑传输文本以及二进制数据。
  1. 功用不同:
  • SSE:只支撑服务器自动推送,客户端只能被迫接收。
  • WebSocket:支撑双向全 duplex 通讯,客户端和服务器都能够自动发送音讯。
  1. 运用场景不同:
  • SSE:适用于需求一对一推送事情的场景。客户端只需监听,服务器自动推送。
  • WebSocket:适用于需求实时双向交互的场景。例如谈天运用。

总的来说:

  • SSE 适用于服务器单向推送文本事情的场景,兼容性稍差但功率高。
  • WebSocket 适用于实时双向通讯的场景,功率更高但兼容性要求高。