菜单

szdxdxdx
szdxdxdx
发布于 2024-05-09 / 95 阅读
0
0

服务端实时推送技术 SSE

介绍

传统的 HTTP 通信遵循请求-响应模式。客户端发起一个请求,服务器返回一个响应。每次通信都是由客户端发起的,服务器在收到请求后作出响应,而不能主动推送数据

在客户端需要实时获取结果的场景下,请求-响应模式模式是不合适的,因为这意味着客户端需要不断地轮询。更好的做法是服务端生成结果之后主动推送给客户端

SSE(Server-Sent Events)协议是基于 HTTP 的扩展,允许服务器在客户端不发起新请求的情况下,主动向客户端推送数据,直到连接被关闭

WebSocket 协议是通过 HTTP 握手来建立连接的,但连接建立后它就从 HTTP 协议切换到 WebSocket 协议,这时不再遵循 HTTP 的请求-响应模式,而是使用全双工的通信模式

传统 HTTP 通信 SSE WebSocket 协议
通信方向 客户端到服务器(单向) 服务器到客户端(单向) 客户端与服务器(双向)
连接模式 短连接,每次请求-响应后关闭 长连接,服务器持续发送数据 持久连接,随时双向通信
协议标准 HTTP/1.0, HTTP/1.1, HTTP/2 基于 HTTP/1.1 始于 HTTP/HTTPS,后切换协议
状态保持 无状态 无状态 保持状态
断线重连 需要客户端重新发起请求 自动重连机制,浏览器会尝试重新连接 需要手动实现或使用库来处理

SSE 较于 WebSocket 更轻量。如果不需要和服务端动态交互,只是希望服务端在有数据的时候推过来,可以使用 SSE 协议

SSE 一般只能传输文本,而 WebSocket 支持传输二进制数据


客户端发起一个标准 HTTP 请求来开启 SSE 会话,要求请求头包含 Accept: text/event-stream 字段,相当于告诉服务端:期望接收 SSE 消息流

服务端返回携带 Content-Type: text/event-stream 字段的响应头,标志着 SSE 连接成功建立。连接会保持开放状态,服务端后续可以随时通过此连接向客户端发送数据

每次返回的数据都由若干个 message,每个 message 内部有若干行组成,每一行都是下述格式中的一种

格式 含义
data: xxxx\n 数据
id: xxxx\n 数据编号
event: xxxx\n 自定义事件
retry: xxxx\n 重连
: xxxx\n 注释

每个 message 之间用 \n\n 分隔

示例

data: {\n
data: "name": "li",\n
data: "age", 24\n
data: }\n\n

返回了一个 message,有四行,每行都是传输了一小段数据


: This is a comment\n\n
data: This is a message\n\n
data: This is another message\n
data: with two lines \n\n

返回了三个 message

  • 第一个 message 有一行,是一个注释
  • 第二个 message 有一行,传输了一段数据
  • 第三个 message 有两行,传输了一段数据

event: foo\n
data: a foo event\n\n
data: an unnamed event\n\n
event: usermessage\n
data: {"name": "li", "age": 24}\n\n

返回了三个 message

  • 第一个 message 有两行,第一行表示触发 foo 事件,第二行传输了一段数据
  • 第二个 message 有一行,传输了一段数据(不指明事件名称默认触发的是 message 事件)
  • 第三个 message 有两行,第一行表示触发 bar 事件,第二行传输了一段数据

代码示例

服务端代码:

var http = require("http");

http.createServer(function (request, response) {

  if (request.url === "/stream") {
    response.writeHead(200, {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      "Connection": "keep-alive",
      "Access-Control-Allow-Origin": "*",
    });

    // 发送一条 message,有一行,指定浏览器重新发起连接的时间间隔
    response.write("retry: 1000\n\n");

    // 发送一条 message,有两行
    // 第一行表示触发 hello 事件,第二行传递一段数据
    response.write("event: hello\n");
    response.write("data: 0\n\n");

    let count = 0;

    // 每隔 500ms 发送一条 message,有一行,传递一段数据
    interval = setInterval(function () {
      count += 1;
      response.write("data: " + count + "\n\n");
    }, 500);

    request.socket.addListener("close", function () {
      clearInterval(interval);
    }, false);
  }
}).listen(4684, "127.0.0.1");

将上述代码保存为 server.js,使用 node 运行,会在本机的 4684 端口打开一个 HTTP 服务。

先运行服务端,再打开下述客户端的网页,就能看到每隔 500ms 接收到一次服务端的数据

客户端代码:

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>SSE client example</title>
</head>
<body>
<pre id="example"></pre>
<script>
  const pre = document.querySelector('pre');

  const source = new EventSource('http://127.0.0.1:4684/stream');
  source.onopen  = (event) => { pre.textContent += "open:\n"; };
  source.onerror = (event) => { pre.textContent += "error.\n"; };

  // hello 事件
  source.addEventListener("hello", (event) => {
    pre.textContent += `hello ${event.data}\n`;
  }, false);
  
  // message 事件
  source.onmessage = (event) => {
    pre.textContent += `${event.data}\n`;
  };
</script>
</body>
</html>

评论