54

所以我理解了服务器发送事件的概念(EventSource):

  • 客户端通过以下方式连接到端点EventSource
  • 客户端只监听从端点发送的消息

我感到困惑的是它在服务器上的工作方式。我看过不同的例子,但想到的是 Mozilla 的:http ://hacks.mozilla.org/2011/06/a-wall-powered-by-eventsource-and-server-sent-事件/

现在这可能只是一个不好的例子,但据我了解,服务器端的工作方式有点道理:

  • 数据存储中的某些更改,例如数据库
  • 服务器端脚本每 N 秒轮询一次数据存储
  • 如果轮询脚本注意到更改,则会向客户端触发服务器发送事件

那有意义吗?从准系统的角度来看,这真的是这样吗?

4

2 回答 2

75

HTML5 医生网站有一篇关于服务器发送事件的精彩文章,但我也会尝试在此处提供一个(合理的)简短摘要。

服务器发送的事件的核心是长时间运行的 http 连接、特殊的 mime 类型 ( text/event-stream) 和提供EventSourceAPI 的用户代理。这些共同构成了服务器和客户端之间单向连接的基础,其中消息可以从服务器发送到客户端

在服务器端,它相当简单。您真正需要做的就是设置以下 http 标头:

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

请务必使用代码200而不是204任何其他代码进行响应,因为这将导致兼容的用户代理断开连接。另外,请确保不要结束服务器端的连接。您现在可以自由地开始将消息推送到该连接。在 nodejs(使用 express)中,这可能类似于以下内容:

app.get("/my-stream", function(req, res) {
    res.status(200)
       .set({ "content-type"  : "text/event-stream"
            , "cache-control" : "no-cache"
            , "connection"    : "keep-alive"
            })

    res.write("data: Hello, world!\n\n")
})

在客户端上,您只需使用EventSourceAPI,如您所述:

var source = new EventSource("/my-stream")
source.addEventListener("message", function(message) {
    console.log(message.data)
})

基本上就是这样。

现在,在实践中,这里实际发生的是服务器和客户端通过相互合同保持连接。只要它认为合适,服务器就会保持连接处于活动状态。如果它愿意,它可能会终止连接并204 No Content在客户端下次尝试连接时做出响应。这将导致客户端停止尝试重新连接。我不确定是否有办法以告知客户端根本不重新连接的方式结束连接,从而跳过客户端尝试重新连接一次。

如前所述,客户端也将保持连接活动,并在断开连接时尝试重新连接。重新连接的算法在规范中指定,并且相当简单。

然而,到目前为止我几乎没有提到的一个超级重要的部分是 mime 类型。mime 类型定义了连接后消息的格式。但是请注意,它并没有规定消息内容的格式,只是规定了消息本身的结构。mime 类型非常简单。消息本质上是信息的键/值对。密钥必须是预定义的集合之一:

  • id - 消息的 id
  • data - 实际数据
  • event - 事件类型
  • retry - 用户代理在重试失败的连接之前应该等待的毫秒数

应忽略任何其他键。然后使用两个换行符分隔消息:\n\n

以下是一条有效消息:(为冗长添加了最后一个新行字符)

data: Hello, world!
\n

客户将看到它:Hello, world!

就像这样:

data: Hello,
data: world!
\n

客户将看到它:Hello,\nworld!

这几乎概括了服务器发送的事件是什么:一个长时间运行的非缓存 http 连接、一个 mime 类型和一个简单的 javascript API。

有关更多信息,我强烈建议阅读规范。它很小,并且很好地描述了事物(尽管可能会更好地总结服务器端的要求。)例如,我强烈建议您阅读它以了解具有某些 http 状态代码的预期行为。

于 2012-08-17T01:56:49.200 回答
6

您还需要确保调用res.flushHeaders(),否则 Node.js 在您调用之前不会发送 HTTP 标头res.end()。有关完整示例,请参阅本教程

于 2020-01-16T22:50:38.437 回答