SSE的实践

221 阅读4分钟

背景

日常业务开发中,大概率会遇到需要和服务端频繁交互的业务,遇到需要数据更新频繁的业务,我们比较常用的是websocket或者轮询来进行数据的获取

这篇文章中,我们着重讨论SSE和websocket的技术选型和实践

Websocket和SSE

Server-Sent Events (SSE) 这项技术最终于2014年10月28日被W3C正式推荐作为标准。SSE旨在解决实时数据传输的需求,尤其是在只需要单向从服务器到客户端的数据流的场景中,提供了一种标准化且相对简易的实现方式。

前端资源占用

SSE

  • 带宽:SSE是单向通信,只能服务器向客户端发送数据,因此带宽利用方面相对专一和高效。
  • CPU资源:相对较低的CPU利用率,因为没有复杂的握手协议和帧管理。
  • 内存资源:每个连接都会占用一定内存,但由于没有复杂的协议处理,内存占用通常较低。

WebSocket

  • 带宽:WebSocket 提供全双工通信,可以同时处理来自客户端的数据和发往客户端的数据,如果客户端和服务器通信频繁,它的带宽利用会更加复杂。
  • CPU资源:相对较高的CPU利用率,因为要处理握手和数据帧的封装/解封装操作。
  • 内存资源:每个WebSocket连接可能会占用更多内存,尤其当消息频繁、数据复杂时。

实际应用中

在实际的应用中,具体哪种技术更节省资源很大程度上取决于应用的使用模式。例如:

  • 对于需要高频双向通信的应用,WebSocket可能会产生更大的资源开销,但提供了所需的双向功能。
  • 对于主要由服务器推送且客户端通信需求较少的应用,SSE将是更高效的选择,并且服务器资源占用可能会较低。 总的来说,SSE通常适用于服务器向客户端单向推送更新的场景,并且资源占用较少。
  • 如果需要订阅多个不同的频道和信息,不妨使用SSE也可以通过参数订阅完成。但是如果需要中途取消部分订阅或者加订阅,那么WebSocket会更合适

兼容性对比

SSE

image.png

WebSocket

image.png

兼容性对比来看 是否需要支持IE 是一个重要的判定因素

使用方法

与普通http接口有什么区别 要怎么改造

普通的http后端接口通常是基于请求-响应模型的,即客户端发送请求,服务器处理后返回响应。然而,对于需要服务器主动向客户端推送数据的应用来说,这种模式就无法满足需求了。

要实现SSE,你需要设置正确的HTTP响应头。

// 这个头部信息用于告诉浏览器不要缓存此次请求的结果。在SSE中,我们通常希望客户端能够即时获取到服务器发送的最新消息,所以需要防止消息被缓存。浏览器中请求头默认开启
'Cache-Control', 'no-cache'

// 这个头部信息告诉浏览器和服务器要保持长连接,这样服务器就可以持续向客户端推送消息,直到连接被关闭。在SSE中,由于需要服务器能持续向客户端发送消息,所以需要保持连接开启。浏览器中请求头默认开启
'Connection', 'keep-alive'

// 这个头部信息告诉浏览器这个请求是一个SSE请求,浏览器会把这个连接保持开启,然后等待服务器推送的消息。这个头部字段是实现SSE的关键。
'Content-Type':'text/event-stream'

Content-Type':'text/event-stream这是用于SSE的特殊MIME媒体类型,并且是SSE规范的一部分。其他的MIME媒体类型或格式,它们并不适用于SSE。如果想要发送JSON,就需要在在客户端解析它们。

DEMO源码

// node.js server.js
const http = require('http');
const fs = require('fs');
const path = require('path');

let eventId = 0;
http.createServer((request, response) => {
    if (request.url === '/index') {
        const filePath = path.join(__dirname, 'index.html');
        fs.readFile(filePath, 'utf8', (err, data) => {
            response.writeHead(200, { 'Content-Type': 'text/html' });
            response.end(data);
        });
    } else if (request.url === '/events') {
        response.writeHead(200, {
            'Content-Type': 'text/event-stream',
            'Cache-Control': 'no-cache',
            'Connection': 'keep-alive'
        });
        setInterval(() => {
            response.write(`id: ${eventId++}\n`);
            response.write(`data: ${new Date().toISOString()}\n\n`);
        }, 1000);
    } else if (request.url === '/nomal') {
        const data = {
            id: eventId++,
            name: `${new Date().toISOString()}`
        };
        response.writeHead(200, { 'Content-Type': 'application/json' });
        response.end(JSON.stringify(data));
    }

}).listen(8000, () => {
    console.log('Server running at http://127.0.0.1:8000/');
});

// index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>SSE-demo</title>
    <script>
      window.onload = function () {
        window.eventSource = new EventSource("http://127.0.0.1:8000/events");
        // 信息接收回调
        eventSource.onmessage = function (event) {
          const p = document.createElement("p");
          p.textContent = "New message: " + event.data;
          document.getElementById("messages").appendChild(p);
        };
        // 处理错误回调
        eventSource.onerror = function (error) {
          const p = document.createElement("p");
          p.textContent = "Error: " + error;
          document.getElementById("messages").appendChild(p);
        };
        // 关闭SSE方法
        window.closeEventSource = () => {
          window.eventSource.close();
        };
      };
    </script>
  </head>
  <body>
    <div id="messages"></div>
  </body>
</html>

上面代码中 分别写了nomal普通接口和SSE接口,可以清晰看出来差别

DEMO截图 image.png