SSE详解

SSE(Server-Sent Events):通俗解释起来就是一种基于HTTP的,以流的形式由服务端持续向客户端发送数据的技术

应用场景

由于HTTP是无状态的传输协议,每次请求需由客户端向服务端建立连接,HTTPS还需要交换秘钥,所以一次请求,建立连接的过程占了很大比例
在http1.1中(1.0也有但未写入标准),虽然增加了keep-alive来保持和服务器的长连接,省去了很多建立连接的过程,但通信过程仍然是应答式1:1的方式,也就是想要获得数据,就必须先发送一个request才能得到一个response,所以在实时监控、推送、视频直播等实时性较高或者带宽利用较苛刻的场景,仍然不是很合适
SSE技术由于能保持连接,并持续接收服务端的数据,所以弥补了这一缺点,与其他类似技术方案相比,短轮询、Coment、WebSocket,在大多数时候,SSE仍然是最好的选择

各技术方案的优缺点

短轮询

短轮询很简单,即客户端定时的向服务端发送请求,如果服务端有数据返回,则返回数据,否则返回空数据

优点:实现简单
缺点:如果想实时性好,则必须轮询间隔短,但会有大量的请求是无效的(返回空数据),如果轮询间隔长,则实时性不好,数据到达客户端的延时最大会趋近于轮询间隔

Coment:一种HACK技术

以即时通信为代表的web应用程序对数据的Low Latency要求,传统的基于轮询的方式已经无法满足,而且也会带来不好的用户体验。于是一种基于http长连接的“服务器推”技术便被hack出来。这种技术被命名为Comet,这个术语由Dojo Toolkit 的项目主管Alex Russell在博文Comet: Low Latency Data for the Browser首次提出,并沿用下来。

Coment技术有两种实现,分别是长轮询(long-polling)基于 Iframe 及 htmlfile 的流(http streaming)方式

1.长轮询(long-polling)

浏览器发出ajax 请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才返回,浏览器JS在处理请求返回信息(超时或有效数据)后再次发出请求,重新建立连接。在此期间服务器端可能已经有新的数据到达,服务器会选择把数据保存,直到重新建立连接,浏览器会把所有数据一次性取回。

优缺点:这种技术没有明显的优缺点,如果非要说,就是需要额外的框架支持吧,且在之前服务端异步编程支持程度并不高的时候,(例如java的servlet3.0之前),后端也需要额外的框架支持

2.基于 Iframe 及 htmlfile 的流

Iframe是html标记,这个标记的src属性会保持对指定服务器的长连接请求,服务器端则可以不停地返回数据,相对于第一种方式,这种方式跟传统的服务器推则更接近。

在第一种方式中,浏览器在收到数据后会直接调用JS回调函数,但是这种方式该如何响应数据呢?可以通过在返回数据中嵌入JS脚本的方式,如“”,服务器端将返回的数据作为回调函数的参数,浏览器在收到数据后就会执行这段JS脚本。

缺点:IE、Morzilla Firefox 下端的进度栏都会显示加载没有完成,而且 IE 上方的图标会不停的转动,表示加载正在进行。

WebSocket

类似TCP socket,参考WebSocket详解

优点:双工通信
缺点:需专门定义数据协议,解析数据流,且部分服务器支持不完善,后台例如java spring boot 2.1.2 仅支持websocket 1.0(最高已达1.3)

SSE

优点:开发简单,和传统的http开发几乎无任何差别,客户端开发简单,有标准支持(EventSource)
缺点:和websocket相比,只能单工通信,建立连接后,只能由服务端发往客户端,且占用一个连接,如需客户端向服务端通信,需额外打开一个连接

其他

在基于spring的开发中,可以使用SseEmitter类进行通信

    @GetMapping(value = "/watch", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public synchronized SseEmitter watch(HttpServletRequest request, @RequestParam("point") String point) throws Exception {
        final HttpSession session = request.getSession();

        //此处超时时间优先级高于servlet容器的request timeout,PS:此超时时间固定,无法通过心跳等其他手段保持连接,超时后 浏览器端默认会重新连接,但SeeEmitter无法复用
        SseEmitter emitter = new SseEmitter(300 * 1000L);

        String key = String.format("watch:%s", point);

        WatchConsumer consumer = new WatchConsumer<>(client, emitter, point);

        if (this.client.watch(point, consumer)) {
            emitter.onCompletion(() -> session.removeAttribute(key));

            emitter.onTimeout(() -> session.removeAttribute(key));

            emitter.onError(throwable -> {
                throwable.printStackTrace();
                session.removeAttribute(key);
            });

            session.setAttribute(key, consumer);

        }

        return emitter;

    }

也可以利用WebFlux,

@GetMapping("/stream-sse")
public Flux> streamEvents() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> ServerSentEvent. builder()
        .id(String.valueOf(sequence))
          .event("periodic-event")
          .data("SSE - " + LocalTime.now().toString())
          .build());
}

但相比之下SseEmitter有OnTimeout和OnCompletion等事件,更加灵活

PS:由于浏览器对于同一个domain,有并发数限制,例如chrome最大是6,长连接会持续性的占用一个连接,同时会占用一个服务器端的一个连接

你可能感兴趣的:(SSE详解)