1. 什么是 Server-Sent Events
1.1. Server-Sent Events 简介
SSE(Server-Sent Event,服务端推送事件)是一种允许服务端向客户端推送新数据的 HTML5 技术。与由客户端每隔几秒从服务端轮询拉取新数据相比,这是一种更优的解决方案。
1.2. SSE 与 WebSocket 相比
同为浏览器推送技术,相较于 WebSocket 而言,Server-Sent Events (简称SSE)更少被人知晓,具体实践也较少。
原因有两点:
- WebSocket 比 SSE 更强大,WebSocket 在客户端和服务器之间建立了双向的实时通信。而 SSE 只支持从服务器到客户端的单向实时通信。
- WebSocket 在浏览器方面支持更广,IE / Edge 几乎根本不支持 SSE。
然而,就第一点而言,与 WebSocket 相比,SSE 也有独特的优势:
- SSE 的浏览器端实现内置断线重连和消息追踪的功能,WebSocket 也能实现,但是不在协议设计范围内,需要手动处理。
- SSE 实现简单,完全复用现有的 HTTP 协议,而 WebSocket 是相对独立于 HTTP 的一套标准,跨平台实现较为复杂。
综合而言,相较于 WebSocket,SSE 基于 HTTP 协议单向工作,更加简单,易用。在一些情况下,使用 SSE 反而是更好的选择。
1.3. 协议实现
SSE 协议很简单,本质上是一个客户端发起的 HTTP Get 请求,服务器在接到该请求后,返回 200 OK 状态,同时附带以下 Headers
1 | Content-Type: text/event-stream |
- SSE 的 MIME Type 规定为 text/event-stream
- SSE 肯定不允许缓存
- SSE 是一个一直打开的 TCP 连接,所以 Connection 为 Keep-Alive
之后,服务器保持连接,在 Body 中持续发送文本流,以实现实时消息推送。
1.4. 浏览器兼容性
2. text/event-stream 文本流
2.1 基础格式
文本流基础格式如下,以行为单位的,以冒号分割 Field 和 Value,每行结尾为 \n
,每行会Trim掉前后空字符,因此 \r\n
也可以。
1 | field: value\n |
注释以冒号打头,格式如下:
1 | : This is a comment\n |
2.2. 事件
事件之间用 额外的\n
隔断(即\n\n
), 每个事件既可以为单行,也可为多行。
2.2.1. 事件分类
下面所示是两个由单行组成的事件:
1 | data: message\n\n |
而这一个是由多行组成的一个事件,更加易读:
1 | data: {\n |
2.2.2. 事件唯一标示
每一个事件可以指定 ID
1 | id: msg1\n |
浏览器会一直跟踪最近的事件 ID,如果发生了重连,浏览器会把最近接收到的事件 ID 放入 HTTP Header Last-Event-ID
中,作为一种简单的同步机制。
2.2.3. 命名事件
除了 ID 唯一标示一个事件之外,也可以通过命名的方式,区分一组类型的事件。默认情况下,事件会被命名为 message
。
1 | event: foo\n |
上面的例子实际上是三个事件,第一个事件命名为 “foo”,第二个事件没有命名,第三个事件命名为”bar”。可以看出,在一个事件内部,”event” 可以放在前面,也可以放在末尾。
2.2.4. 重连时间
一般情况下,连接中断的时候,客户端会在 3 秒内进行重连,这个时间也可以由服务器来指定
1 | retry: 10000\n |
3. 客户端 API
3.1. 检测 SSE 支持
一般可以通过检测 EventSource 对象是否存在来判定当前浏览器是否支持 SSE
1 | function supportsSSE() { |
3.2. 连接事件源
直接创建 EventSource 对象即可,创建完成后,浏览器会及时打开。
1 | new EventSource(url); |
事件源连接后会发送 open
事件,可以用两种方式监听:
1 | source.onopen = function(event) { |
3.3. 接收事件
和上面类似,有两种方式可以接收事件。浏览器会自动把一个消息中的多个分段拼接成一个完整的字符串,因此,可以轻松地在这里使用 JSON 序列化和反序列化处理。
1 | source.onmessage = function(event) { |
3.4. 命名事件
命名事件不会由 message
监听触发,而是使用独立的监听:
1 | source.addEventListener("foo", function(event) { |
3.5. 错误处理
1 | source.onerror = function(event) { |
3.6. 主动断开连接
1 | source.close(); |
3.7. 连接状态
1 | switch (source.readyState) { |
4. 服务端实现
要在服务器端实现 SSE 必须要注意,SSE 为每个用户保持了一个 TCP 连接,这就意味着Apache 之类的基于 线程/进程 的服务器引擎不适合这个工作。而 Node.js 绝对是最佳选择。
5. 简单实例
5.1. 服务端 server.js
1 | var http = require("http") |
5.2. 客户端
1 |
|