前言
同为浏览器推送技术,相较于 WebSocket 而言,Server-Sent Events (简称SSE)更少被人知晓,具体实践也较少。
原因有两点:
- WebSocket 比 SSE 更强大,Websocket 在客户端和服务器之间建立了双向的实时通信。而 SSE 只支持从服务器到客户端的单向实时通信。
- WebSocket 在浏览器方面支持更广(详见下图),IE / Edge 几乎根本不支持 SSE

然而,就第一点而言,与 WebSocket 相比,SSE 也有独特的优势。
- SSE 的浏览器端实现内置断线重连和消息追踪的功能,WebSocket 也能实现,但是不在协议设计范围内,需要手动处理。
- SSE 实现简单,完全复用现有的 HTTP 协议,而 WebSocket 是相对独立于 HTTP 的一套标准,跨平台实现较为复杂。
协议实现
SSE 协议很简单,本质上是一个客户端发起的 HTTP Get 请求,服务器在接到该请求后,返回 200 OK 状态,同时附带以下 Headers
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
- SSE 的 MIME Type 规定为 text/event-stream
- SSE 肯定不允许缓存
- SSE 是一个一直打开的 TCP 连接,所以 Connection 为 Keep-Alive
协议实现
SSE 协议很简单,本质上是一个客户端发起的 HTTP Get 请求,服务器在接到该请求后,返回 200 OK 状态,同时附带以下 Headers
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
- SSE 的 MIME Type 规定为 text/event-stream
- SSE 肯定不允许缓存
- SSE 是一个一直打开的 TCP 连接,所以 Connection 为 Keep-Alive
之后,服务器保持连接,在 Body 中持续发送文本流,以实现实时消息推送。
基础格式
文本流基础格式如下,以行为单位的,以冒号分割 Field 和 Value,每行结尾为 \n,每行会Trim掉前后空字符,因此 \r\n 也可以。
field: value\n
注释以冒号打头,格式如下
: This is a comment\n
事件
事件之间用 额外的\n 隔断, 每个事件既可以为单行,也可为多行。
下面所示是两个由单行组成的事件
data: message\n\n
data: message2\n\n
而这一个是由多行组成的一个事件,更加易读
data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n
事件唯一标示
每一个事件可以指定 ID
id: msg1\n
data: message\n\n
浏览器会一直跟踪最近的事件ID,如果发生了重连,浏览器会把最近接收到的事件ID放入 HTTP Header “Last-Event-ID” 中,作为一种简单的同步机制。
命名事件
除了 ID 唯一标示一个事件之外,也可以通过命名的方式,区分一组类型的事件。默认情况下,事件会被命名为 “message”。
event: foo\n
data: a foo event\n\n
data: an unnamed event\n\n
data: a bar event\n
event: bar\n\n
上面的例子实际上是三个事件,第一个事件命名为 “foo”,第二个事件没有命名,第三个事件命名为”bar”。可以看出,在一个事件内部,”event” 可以放在前面,也可以放在末尾。
重连时间
一般情况下,连接中断的时候,客户端会在 3 秒内进行重连,这个时间也可以由服务器来指定
retry: 10000\n
服务器实现
要在服务器端实现 SSE 必须要注意,SSE 为每个用户保持了一个 TCP 连接,这就意味着Apache 之类的基于 线程/进程 的服务器引擎不适合这个工作。
而 Node.js 绝对是最佳人选。
具体示例可以参考这篇文章 Server-Sent Events in Node.js
浏览器调用
检测SSE支持
一般可以通过检测 EventSource 对象是否存在来判定当前浏览器是否支持 SSE
function supportsSSE() {
return !!window.EventSource;
}
连接事件源
直接创建 EventSource 对象即可,创建完成后,浏览器会及时打开。
new EventSource(url);
事件源连接后会发送 “open” 事件,可以用两种方式监听
source.onopen = function(event) {
// handle open event
};
source.addEventListener("open", function(event) {
// handle open event
}, false);
接收事件
和上面类似,有两种方式可以接收事件。浏览器会自动把一个消息中的多个分段拼接成一个完整的字符串,因此,可以轻松地在这里使用 JSON 序列化和反序列化处理。
source.onmessage = function(event) {
var data = event.data;
var origin = event.origin;
var lastEventId = event.lastEventId;
// handle message
};
source.addEventListener("message", function(event) {
var data = event.data;
var origin = event.origin;
var lastEventId = event.lastEventId;
// handle message }, false);
命名事件
命名事件不会由 “message” 监听触发,而是使用独立的监听
source.addEventListener("foo", function(event) {
var data = event.data;
var origin = event.origin;
var lastEventId = event.lastEventId;
// handle message
}, false);
错误处理
source.onerror = function(event) {
// handle error event
};
source.addEventListener("error", function(event) {
// handle error event
}, false);
主动断开连接
source.close();
连接状态
switch (source.readyState) {
case EventSource.CONNECTING:
// do something
break;
case EventSource.OPEN:
// do something
break;
case EventSource.CLOSED:
// do something
break;
default:
// this never happens
break;
}
结论
综合而言,相较于 WebSocket,SSE 基于 HTTP 协议单向工作,更加简单,易用。在一些情况下,使用 SSE 反而是更好的选择。