weoyk / note

0 stars 0 forks source link

还在用轮询、websocket查询大屏数据?sse用起来轮询、websocket、sse大对比以及使用场景的区别。sse - 掘金 #41

Open weoyk opened 16 hours ago

weoyk commented 16 hours ago

常见的大屏数据请求方式

1、http请求轮询:使用定时器每隔多少时间去请求一次数据。优点:简单,传参方便。缺点:数据更新不实时,浪费服务器资源(一直请求,但是数据并不更新)
2、websocket:使用websocket实现和服务器长连接,服务器向客户端推送大屏数据。优点:长连接,客户端不用主动去请求数据,节约服务器资源(不会一直去请求数据,也不会一直去查数据库),数据更新及时,浏览器兼容较好(web、h5、小程序一般都支持)。缺点:有点大材小用,一般大屏数据只需要查询数据不需要向服务端发送消息,还要处理心跳、重连等问题。

3、sse:基于http协议,将一次性返回数据包改为流式返回数据。优点:sse使用http协议,兼容较好、sse轻量,使用简单、sse默认支持断线重连、支持自定义响应事件。缺点:浏览器原生的EventSource不支持设置请求头,需要使用第三方包去实现(event-source-polyfill)、需要后端设置接口的响应头Content-Type: text/event-stream

sse和websocket的区别

  1. websocket支持双向通信,服务端和客户端可以相互通信。sse只支持服务端向客户端发送数据。
  2. websocket是一种新的协议。sse则是基于http协议的。
  3. sse默认支持断线重连机制。websocket需要自己实现断线重连。
  4. websocket整体较重,较为复杂。sse较轻,简单易用。

Websocket和SSE分别适用于什么业务场景?

根据sse的特点(轻量、简单、单向通信)更适用于大屏的数据查询,业务应用上查询全局的一些数据,比如消息通知未读消息等。

根据websocket的特点(双向通信)更适用于聊天功能的开发

前端代码实现

sse的前端的代码非常简单

 const initSse = () => {
        const source = new EventSource(`/api/wisdom/terminal/stats/change/notify/test`);

        source.addEventListener('stats_change', function (event: any) {
            const types = JSON.parse(event.data).types;
        });

        source.onopen = function () {
            console.log('SSE 连接已打开');
        };

        source.onerror = function (error: any) {
            console.error('SSE 连接错误:', error);
        };
        setSseSource(source);
    };

sseSource.close();

这种原生的sse连接是不能设置请求头的,但是在业务上接口肯定是要鉴权需要传递token的,那么怎么办呢? 我们可以使用event-source-polyfill这个库

 const source = new EventSourcePolyfill(`/api/wisdom/terminal/stats/change/notify/${companyId}`, {
            headers: {
                Authorization: sessionStorage.get(StorageKey.TOKEN) || storage.get(StorageKey.TOKEN),
                COMPANYID: storage.get(StorageKey.COMPANYID),
                COMPANYTYPE: 1,
                CT: 13
            }
        });

后端代码实现

后端最关键的是设置将响应头的Content-Type设置为text/event-streamCache-Control设置为no-cacheConnection设置为keep-alive。每次发消息需要在消息体结尾用"/n/n"进行分割,一个消息体有多个字段每个字段的结尾用"/n"分割。

var http = require("http");

http.createServer(function (req, res) {
  var fileName = "." + req.url;

  if (fileName === "./stream") {
    res.writeHead(200, {
      "Content-Type":"text/event-stream",
      "Cache-Control":"no-cache",
      "Connection":"keep-alive",
      "Access-Control-Allow-Origin": '*',
    });
    res.write("retry: 10000\n");
    res.write("event: connecttime\n");
    res.write("data: " + (new Date()) + "\n\n");
    res.write("data: " + (new Date()) + "\n\n");

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

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

其它开发中遇到的问题

我在开发调试中用的是umi,期间遇到个问题就是sse连接上了但是在控制台一直没有返回消息,后端那边又是正常发出了的,灵异的是在后端把服务干掉的一瞬间可以看到控制台一下接到好多消息。我便怀疑是umi的代理有问题,然后我就去翻umi的文档,看到了下面的东西:

一顿操作之后正常