Server-sent Events 如何实现对多个客户端的推送

在现代 Web 应用中,对于实时的数据更新是非常重要的。传统的轮询方式并不令人满意,因为它会导致浪费宝贵的网络带宽和服务器资源。 Server-Sent Events (SSE)是一种基于 HTTP 协议的技术,它能够实现从服务端实时推送数据给客户端,而不需要客户端不停地向服务端发起请求。

SSE 技术非常简单,它只是一个单向的持久化的连接。具有以下优点:

  • 支持跨域,仅基于HTTP协议
  • 实时推送数据到客户端
  • 支持失败重试

SSE 的基本原理

SSE 基于 HTTP 协议,使用长轮询技术,创建一个单向、持久化的连接,从服务器实时推送数据给客户端。因为这个连接是单向的,所以客户端不能通过这个连接向服务器发送任何数据。 SSE 的架构如下所示:

  • 浏览器通过 HTTP 请求与服务器建立 SSE 连接。
  • 服务器返回的响应头中带有 "Content-Type: text/event-stream",以指定该响应为 SSE 响应。
  • 服务器发送包含数据的文本(events)通过 SSE 连接。
  • 浏览器接收到这些数据,触发事件(Event)从而更新网页。

SSE 的事件流

SSE 数据通过事件流(Event stream)的方式传递。 事件流是一系列以 "\n"(换行字符)分隔的 event 字符串。 event 字符串包含事件类型和数据,如下所示:

event: message
data: Hello, world!

在这个示例中,我们定义了一个名为 "message" 的事件,并将 "Hello, world!" 作为数据传输。

“event”的 字段,指定了该事件类型,而“data”字段是该事件的数据字段。当服务器发送一个事件过来时,客户端的 JavaScript 代码接收这个event字符串,并对其进行解析。

SSE 连接的建立和维持

建立 SSE 连接很简单。 客户端代码只需要像发送 AJAX 请求一样发送一个 HTTP Get 请求,并接受响应,只需设置一些特殊的响应头信息。

// 浏览器端的JS代码
const evtSource = new EventSource("/api/sse/path");
evtSource.onmessage = function(e) {
  console.log("message", e.data);
};

浏览器端向 "/api/sse/path" 发送了一个 GET 请求,该请求将被服务器解析为 SSE 连接,然后在响应头中返回以下信息:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

服务器就开始向该 SSE 连接发送数据。

为了保持这个 SSE 连接的存活,浏览器和服务器之间需要发送数据。 如果 SSE 连接在一段时间内未能接收到数据,它就会被浏览器视为关闭,或假死。 因此,我们需要在服务端定期发送“心跳”事件,以保持连接的存活。

SSE 的重连机制

如果 SSE 连接的数据传输被中断,客户端会自动尝试重新连接,从而保证 SSE 的长时间存活。

  • SSE 与 WebSocket 的区别

WebSocket 是一种双向通信协议,而 SSE 在一种单向通信协议。

WebSocket 其实实现了一个长轮询,只是协议和头有所改变。而SSE是对长轮询的封装形式。并且WebSocket比SSE更为强大,可以扩展和升级之前的HTTP协议。换句话说,WebSocket 支持双向通信,而 SSE 仅支持单向,而且 WebSocket 比 SSE 更容易扩展和升级。

SSE 在多个客户端中的应用

在实际应用中,需要将 SSE 连接从服务端传递给多个客户端。这通常可以通过存储 SSE 连接的列表来实现。当新的客户端到达时,服务器会将 SSE 连接添加到列表中。 然后,在服务器发送 SSE 事件时,它将转发事件给所有客户端列表中的 SSE 连接。

下面是一个 Node.js 服务器示例,在这个示例中,我们使用 Node.js 的 Express 框架来处理 SSE 请求,使用 Node.js 内置的 EventEmitter 进行事件管理:

// 服务器端Node.js代码
const express = require("express");
const EventEmitter = require("events").EventEmitter;
const app = express();

// 用来存储 SSE 连接的事件管理对象
const sseEvent = new EventEmitter();

// 将新连接加入到连接列表中
app.get("/api/sse", (req, res) => {
  res.set({
    "Content-Type": "text/event-stream",
    "Cache-Control": "no-cache",
    Connection: "keep-alive",
  });
  sseEvent.on("message", (data) => {
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  });
});

// 在一个客户端上发送消息
app.post("/api/sse/message", (req, res) => {
  const data = req.body;
  sseEvent.emit("message", data);
  res.status(200).send("Success");
});

app.listen(8888, () => {
  console.log("Server started on port 8888");
});

在客户端上的 JavaScript 代码如下所示:

// 浏览器端的JS代码
const events = new EventSource("/api/sse");
events.onmessage = function(e) {
  console.log("message", e.data);
};

// 点击按钮发送请求
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function() {
  if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
    console.log("request complete", xhr.responseText);
  }
};
xhr.open("POST", "/api/sse/message");
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify({ message: "Hello from client!" }));

在这个示例中,当客户端发送 POST 请求到服务器时,服务器会将其转发到连接列表中的每个 SSE 连接。 SSE 连接会将数据转发给所有客户端,在这个示例中,将在浏览器中的 console.log() 中打印消息。

总结

使用 SSE 技术可以实现服务器对多个客户端实时推送数据,从而提高系统的实时性。使用 SSE 技术可能需要一些服务器端的支持,但是它可以通过简单的 JavaScript 代码轻松地集成到任何现代 Web 应用程序中。

来源:JavaScript中文网 ,转载请注明来源 本文地址:https://www.javascriptcn.com/post/6590d98aeb4cecbf2d61fd52


纠错
反馈