后端消息推送-SSE协议

Posted sjpqy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了后端消息推送-SSE协议相关的知识,希望对你有一定的参考价值。

介绍

  HTTP 服务器推送也称 HTTP 流,是一种客户端-服务器通信模式,它将信息从 HTTP 服务器异步推送到客户端,而无需客户端请求。现在的 web 和 app 中,越来越多的场景使用这种通信模式,比如实时的消息提醒,IM在线聊天,多人文档协作等。以前实现这种类似的功能一般都是用ajax长轮询,而现在我们有了新的、更优雅的选择 —— WebSocket 和 SSE。

  • WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
  • SSE 是 Server-Sent Events 的简称, 是一种服务器端到客户端(浏览器)的单项消息推送。对应的浏览器端实现 Event Source 接口被制定为HTML5 的一部分。不过现在IE不支持该技术。相比于 WebSocket,SSE 简单很多,服务器端和客户端工作量都要小很多、简单很多,同时实现的功能也要有局限。

SSE与WebSocket有相似功能,都是用来建立浏览器与服务器之间的通信渠道。两者的区别在于:

  • WebSocket是全双工通道,可以双向通信,功能更强;SSE是单向通道,只能服务器向浏览器端发送。
  • WebSocket是一个新的协议,需要服务器端支持;SSE则是部署在 HTTP协议之上的,现有的服务器软件都支持。
  • SSE是一个轻量级协议,相对简单;WebSocket是一种较重的协议,相对复杂。
  • SSE默认支持断线重连,WebSocket则需要额外部署。
  • SSE支持自定义发送的数据类型。
  • SSE不支持CORS,参数url就是服务器网址,必须与当前网页的网址在同一个网域(domain),而且协议和端口都必须相同。WebSocket支持

本文介绍SSE的使用方式(如果系统中对这种消息的准确性和可靠性有严格的要求,则使用websocket,websocket的使用相对复杂的多)
如果想了解SSE的详细基础知识,可以参考阮一峰老师的这篇文章:Server-Sent Events 教程

SSE后端代码实现

SpringMVC中,已经集成了该功能,所以无需额外引入jar包,直接上代码:

@RestController
@RequestMapping("/notice")
public class NoticeController 

    @Autowired
    private NoticeService noticeService;

    @GetMapping(path = "createSseEmitter")
    public SseEmitter createSseEmitter(String id) 
        return noticeService.createSseEmitter(id);
    

    @PostMapping(path = "sendMsg")
    public boolean sendMsg(String id, String content) 
        noticeService.sendMsg(id, content);
        return true;
    



@Slf4j
@Service
public class NoticeServiceImpl implements NoticeService 
    @Autowired
    @Qualifier("sseEmitterCacheService")
    private CacheService<SseEmitter> sseEmitterCacheService;

    @Override
    public SseEmitter createSseEmitter(String clientId) 
        if (StringUtil.isBlank(clientId)) 
            clientId = UUID.randomUUID().toString().replace("-", "");
        
        SseEmitter sseEmitter = sseEmitterCacheService.getCache(clientId);
        log.info("获取SSE,id=", clientId);
        final String id = clientId;
        sseEmitter.onCompletion(() -> 
            log.info("SSE已完成,关闭连接 id=", id);
            sseEmitterCacheService.deleteCache(id);
        );
        return sseEmitter;
    
    @Override
    public void sendMsg(String clientId, String content) 
        if (sseEmitterCacheService.hasCache(clientId)) 
            SseEmitter sseEmitter = sseEmitterCacheService.getCache(clientId);
            try 
                sseEmitter.send(content);
             catch (IOException e) 
                log.error("发送消息失败:", e.getMessage(), e);
                throw new BusinessRuntimeExcepption(CustomExcetionConstant.IO_ERR, "发送消息失败", e);
            
         else 
            log.error("SSE对象不存在");
            throw new BusinessRuntimeExcepption("SSE对象不存在");
        
    

这里,只列出了核心的代码,简而言之,需要做到两点即可:

  1. 前端首先是发起一个请求,创建SseEmitter,即createSseEmitter方法,该方法必须返回一个SseEmitter对象;
  2. 返回的SseEmitter,后端必须要缓存起来(我用的是ehcache,也可以直接定义一个map来缓存);

前端代码

使用浏览器原生提供的方法即可:

const url = \'/xx/xxx\'
// 1. 创建实例
var source = new EventSource(url)

// 2. 事件监听
// 建立连接后,触发`open` 事件
source.addEventListener(\'open\', (e) => 
    console.log(\'open\', e)
)
// 收到消息,触发`message` 事件
source.addEventListener(\'message\', (e) => 
    console.log(\'message\', e)
)
// 发生错误,触发`error` 事件
source.addEventListener(\'error\', (e) => 
    console.log(\'error\', e)
)
// 自定义事件
source.addEventListener(\'eventName\', (e) => 
  // ...
, false)

// 3. 关闭链接
source.close()

由于,我请求该接口,需要带上token,所以直接使用EventSource不行,另外这个IE也不支持。所以选择了一个工具:event-source-polyfill

1. 先安装 event-source-polyfill

npm install event-source-polyfill --save

2. 使用

import  EventSourcePolyfill  from "event-source-polyfill";

createSource() 
  const url = \'/xx/xx/xx\'  
  const source = new EventSourcePolyfill(url, 
    headers: 
      token: \'xxxxx\'
    
  )

  source.addEventListener(\'open\', (e) => 
    console.log(\'open\', e)
  )
  source.addEventListener(\'message\', (e) => 
    console.log(\'message\', e)
  )
   source.addEventListener(\'error\', (e) => 
    console.log(\'error\', e)
  )

注意

前端配置了代理,所以一直收不到后端发送的消息,尝试加入以下参数:

// vue.config.js

module.exports = 
  // ...
  devServer: 
    compress: false,
    ....
    

 

HTTP 服务器消息推送之SSE



WebSocket是HTML5开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。在WebSocket API中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。


SSE 是 Server-Sent Events 的简称, 是一种服务器端到客户端(浏览器)的单项消息推送。对应的浏览器端实现 Event Source 接口被制定为HTML5 的一部分。不过现在IE不支持该技术。相比于 WebSocket,SSE 简单很多,服务器端和客户端工作量都要小很多、简单很多,同时实现的功能也要有局限。



SSE&WebSocket



SSE与WebSocket有相似功能,都是用来建立浏览器与服务器之间的通信渠道。两者的区别在于:


  • WebSocket是全双工通道,可以双向通信,功能更强;SSE是单向通道,只能服务器向浏览器端发送。

  • WebSocket是一个新的协议,需要服务器端支持;SSE则是部署在 HTTP协议之上的,现有的服务器软件都支持。

  • SSE是一个轻量级协议,相对简单;WebSocket是一种较重的协议,相对复杂。

  • SSE默认支持断线重连,WebSocket则需要额外部署。

  • SSE支持自定义发送的数据类型。

  • SSE不支持CORS,参数url就是服务器网址,必须与当前网页的网址在同一个网域(domain),而且协议和端口都必须相同。WebSocket支持 




客户端 EventSource


API

[Constructor(DOMString url, optional EventSourceInit eventSourceInitDict)]interface EventSource : EventTarget { readonly attribute DOMString url; readonly attribute boolean withCredentials;

// ready state const unsigned short CONNECTING = 0; const unsigned short OPEN = 1; const unsigned short CLOSED = 2; readonly attribute unsigned short readyState;

// networking attribute EventHandler onopen; attribute EventHandler onmessage; attribute EventHandler onerror;

void close();};

dictionary EventSourceInit { boolean withCredentials = false;};


基本用法


1. 创建EventSource 实例


var source = new EventSource(url)

2. 事件监听


// 建立连接后,触发`open` 事件source.onopen = (event)=>{ // ...}

// 收到消息,触发`message` 事件source.onmessage = (event)=>{ // ...}

// 发生错误,触发`error` 事件source.onerror = (event)=>{ // ...}

// 自定义事件source.addEventListener('eventName', event => { // ...}, false)


3. 关闭连接


source.close()


服务器端开发


响应头设置

SSE的相应,需要设置如下的Http头信息

Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-alive

第一行,Content-Type指定的 MIME类型必须为 text/event-stream


消息格式

SSE 推送的消息必须是UTF-8编码的纯文本。每次推送有若干个事件消息组成,每个事件消息之间用两个换行(\n\n)分割。每个事件消息又有若干行组成,每行格式以键值对形式组成:

[key]: value\n


key有一下几个值可取:

  • data: 消息内容

  • event: 消息事件名称,默认为 message,浏览器可以用 addEventListener()监听该事件。

  • id: 消息编号。浏览器用lastEventId属性读取这个值。一旦连接断线,浏览器会发送一个 HTTP 头,里面包含一个特殊的Last-Event-ID头信息,将这个值发送回来,用来帮助服务器端重建连接。因此,这个头信息可以被视为一种同步机制

  • retry: 浏览器重新发起连接的时间间隔。


示例


浏览器端代码

// index.jsvar source = new EventSource('/stream');source.onmessage = function(event) { var message = event.data;  // do stuff based on received message };

服务器端代码(nodejs)

var express = require('express')var fs = require('fs')var app = express()

app.get('/stream', (req, res) => { res.writeHead(200, { "Content-Type": "text/event-stream", "Cache-Control": "no-cache", "Connection": "keep-alive" });

var interval = setInterval(function () { res.write("data: " + (new Date()) + "\n\n"); }, 1000);

req.connection.addListener("close", function () { clearInterval(interval); }, false);})

app.listen(9999, (err) => { if (err) { console.log(err) return } console.log('listening on port 9999')})


推荐阅读




好文我在看

以上是关于后端消息推送-SSE协议的主要内容,如果未能解决你的问题,请参考以下文章

PHP+SSE服务器向客户端推送消息

HTTP 服务器消息推送之SSE

基于 SSE 实现服务端消息主动推送解决方案

基于 SSE 实现服务端消息主动推送解决方案

Springboot集成SSE实现消息推送之单工通信

长轮询,iframe和sse三种web消息实时推送demo实践