后端消息推送-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,
    ....
  }  
}

 

posted on 2023-05-11 17:45  sjpqy  阅读(3304)  评论(0编辑  收藏  举报

导航