使用服务器发送的事件流式传输更新

服务器发送的事件 (SSE) 通过 HTTP 协议从服务器向客户端发送自动更新, 连接。建立连接后,服务器即可启动数据 传输。

您可以使用 SSE 从 Web 应用发送推送通知。 SSE 单向发送信息,因此您不会收到 客户端。

SSE 的概念可能比较熟悉。Web 应用“订阅”输入流 服务器生成的更新,并且每当发生新事件时, 会发送给客户端但要真正理解服务器发送的事件 了解其 AJAX 前身的局限性。其中包括:

  • 轮询:应用反复轮询服务器以获取数据。这种方法 被大多数 AJAX 应用使用。使用 HTTP 协议 数据围绕请求和响应格式展开。客户端发出请求 并等待服务器返回数据如果没有可用数据,则为空, 响应。额外轮询会产生更大的 HTTP 开销。

  • 长轮询(挂起 GET / COMET):如果服务器没有数据 则服务器会将请求保持为待处理状态,直到有新数据可用为止。 因此,这种方法通常称为“Hanging GET”。时间 服务器响应、关闭连接 然后重复这个过程因此,服务器会不断 新数据。为此,开发者通常会使用黑客手段,例如将 脚本标记更改为“无限”iframe。

服务器发送的事件从设计之初就是为了提高效率。 与 SSE 通信时,服务器可将数据推送到您的 而无需发出初始请求。也就是说, 当更新发生时,可以从服务器流式传输到客户端。SSE 在服务器和客户端之间打开一个单向通道。

服务器发送的事件和长轮询的主要区别在于,SSE 由浏览器直接处理,用户只需监听消息。

服务器发送事件与 WebSocket 对比

为什么要选择服务器发送的事件而不是 WebSocket?这个问题问得好。

WebSockets 具有丰富的协议, 双向、全双工通信双向渠道更适合 游戏、即时通讯应用,以及任何需要近乎实时更新的应用场景, 两个方向。

但是,有时您只需要从服务器进行单向通信。 例如,当朋友更新了自己的状态、股票行情、新闻信息流或 其他自动化数据推送机制也就是说, 对客户端 Web SQL 数据库或 IndexedDB 对象存储的更新。 如果您需要向服务器发送数据,XMLHttpRequest 始终是理想之选。

SSE 通过 HTTP 发送。没有特殊的协议或服务器 才能开始运作。WebSocket 需要全双工 连接和新的 WebSocket 服务器来处理协议。

此外,服务器发送的事件具有 WebSocket 所缺少的各种功能 包括自动重新连接、事件 ID 以及发送 任意事件

使用 JavaScript 创建 EventSource

如需订阅事件流,请创建一个 EventSource 对象,并将 你的直播网址:

const source = new EventSource('stream.php');

接下来,为 message 事件设置处理程序。您可以选择性地 监听 openerror

source.addEventListener('message', (e) => {
  console.log(e.data);
});

source.addEventListener('open', (e) => {
  // Connection was opened.
});

source.addEventListener('error', (e) => {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
});

从服务器推送更新时,会触发 onmessage 处理程序 并且新数据可在其 e.data 资源中查看。更神奇的是 只要连接关闭,浏览器就会自动重新连接到 并在大约 3 秒后显示来源您的服务器实施甚至可以控制 重新连接超时

大功告成。您的客户端现在可以处理来自 stream.php 的事件。

事件流格式

从来源发送事件流就是构建一个 使用 text/event-stream Content-Type 提供的纯文本响应, 遵循 SSE 格式的要求 响应的基本形式应包含 data: 行,后跟 后接两个“\n”字符结束直播:

data: My message\n\n

多行数据

如果邮件较长,可以使用多行 data: 将其拆分。 以 data: 开头的两行或更多连续行将被视为 单个数据,即仅触发一个 message 事件。

每行都应以一个“\n”结尾(最后一个除外,它应该以 。传递给 message 处理程序的结果是单个字符串 并由换行符连接。例如:

data: first line\n
data: second line\n\n</pre>

这将生成“第一行\n 第二行”在 e.data 中。然后,您可以使用 e.data.split('\n').join(''),用于重建不含“\n”的消息字符。

发送 JSON 数据

使用多行有助于您在不破坏语法的情况下发送 JSON:

data: {\n
data: "msg": "hello world",\n
data: "id": 12345\n
data: }\n\n

可能用于处理该数据流的客户端代码:

source.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  console.log(data.id, data.msg);
});

将 ID 与活动相关联

您可以添加一个以 id::

id: 12345\n
data: GOOG\n
data: 556\n\n

设置 ID 可让浏览器跟踪最后触发的事件,以便在 与服务器的连接中断,系统会发送一个特殊的 HTTP 标头 (Last-Event-ID), 新请求所设置的值这样,浏览器就可以确定适合触发的事件。 message 事件包含 e.lastEventId 属性。

控制重新连接超时

浏览器会在大约 3 秒内尝试重新连接到来源 会在每次连接关闭后触发您可以通过添加 以 retry: 开头,后跟毫秒数的行 等待片刻,然后再尝试重新连接。

以下示例会在 10 秒后尝试重新连接:

retry: 10000\n
data: hello world\n\n

指定事件名称

单个事件来源可以通过添加 事件名称。如果存在以 event: 开头的行, 及事件的唯一名称,则该事件会与该名称相关联。 在客户端上,您可以设置一个事件监听器来监听该特定事件。

例如,以下服务器输出会发送三种类型的事件: 通用的“讯息”事件、“userlogon”和“update”事件:

data: {"msg": "First message"}\n\n
event: userlogon\n
data: {"username": "John123"}\n\n
event: update\n
data: {"username": "John123", "emotion": "happy"}\n\n

在客户端设置事件监听器后:

source.addEventListener('message', (e) => {
  const data = JSON.parse(e.data);
  console.log(data.msg);
});

source.addEventListener('userlogon', (e) => {
  const data = JSON.parse(e.data);
  console.log(`User login: ${data.username}`);
});

source.addEventListener('update', (e) => {
  const data = JSON.parse(e.data);
  console.log(`${data.username} is now ${data.emotion}`);
};

服务器示例

以下是采用 PHP 进行的基本服务器实现:

<?php
header('Content-Type: text/event-stream');
header('Cache-Control: no-cache'); // recommended to prevent caching of event data.

/**
* Constructs the SSE data format and flushes that data to the client.
*
* @param string $id Timestamp/id of this connection.
* @param string $msg Line of text that should be transmitted.
**/

function sendMsg($id, $msg) {
  echo "id: $id" . PHP_EOL;
  echo "data: $msg" . PHP_EOL;
  echo PHP_EOL;
  ob_flush();
  flush();
}

$serverTime = time();

sendMsg($serverTime, 'server time: ' . date("h:i:s", time()));
?>

下面是一个在 Node JS 上使用 Express 处理程序:

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

    // Sends an event to the client where the data is the current date,
    // then schedules the event to happen again after 5 seconds.
    const sendEvent = () => {
        const data = (new Date()).toLocaleTimeString();
        res.write("data: " + data + '\n\n');
        setTimeout(sendEvent, 5000);
    };

    // Send the initial event immediately.
    sendEvent();
});

sse-node.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <script>
    const source = new EventSource('/events');
    source.onmessage = (e) => {
        const content = document.createElement('div');
        content.textContent = e.data;
        document.body.append(content);
    };
    </script>
  </body>
</html>

取消事件流

通常,在连接时,浏览器会自动重新连接到事件来源, 但该行为可以从客户端或服务器取消。

如需从客户端取消数据流,请调用:

source.close();

如需从服务器取消数据流,请使用非 text/event-stream 进行响应 Content-Type,或返回非 200 OK 的 HTTP 状态 (例如 404 Not Found)。

这两种方法都可以防止浏览器重新建立连接。

安全简介

EventSource 生成的请求受与 例如提取如果您需要将服务器上的 SSE 端点 以及如何从不同的源访问 跨域资源共享 (CORS)