SSE,全称Server-Sent Events,作为一种半双工的前后端通信方式,由于实现方式简单、轻量,在后端向前端的主动推送场景中具备很好的应用效果,笔者最近在一个项目中多有使用,过程中也是查阅了不少资料和文档,也有所感悟,在此将整体做一次综述。这是一篇系列文章,共分三篇,这是第一篇。

引言

在性能可接受的web应用程序中,没有简单、通用的方法来实现服务端到客户端的异步通信。

HTTP是B/S中的一种请求-响应协议。客户端(通常是浏览器)向服务端提交请求,服务端向客户端返回一个响应。服务端只能向发出请求的客户端发送响应。在HTTP协议中,客户端是消息交换的发起者。

在某些情况下,需要服务端成为消息交换的发起方。实现这一点的方法之一是允许服务端将消息推送到支持发布/订阅模式的客户端中。客户端从服务端订阅消息,服务端向许多订阅的客户端发送消息(一旦消息可用)。要停止交换,客户端需要取消订阅。

SSE(服务器发送事件)就是一种实现上述场景的技术。

概述

有几种技术允许客户端从服务端接收有关异步更新的消息。它们可以分为两类:客户端拉取和服务端推送。

客户端拉取

在客户端拉取技术中,客户端通过短轮询或者长轮询定期请求服务器进行更新。

短轮询

客户端定期向服务端发送请求。如果服务端有更新,它会向客户端发送响应并关闭连接。如果服务端没有更新,它会向客户端发送一个特殊响应,并关闭连接。

长轮询

客户端向服务端发送请求。如果服务端有更新,它会向客户端发送响应并关闭连接。如果服务端没有更新,它会保持连接,直到更新可用为止。当更新可用时,服务端向客户端发送响应并关闭连接。如果更新在一段时间内不可用,服务端将向客户端发送一个特殊响应,并关闭连接。

服务端推送

在服务端推送技术中,服务端在消息可用后立即主动地向客户端发送消息。其中,有两种类型的服务端推送:SSEWebSocket

SSE

SSE是一种仅发送文本消息的技术。SSE基于HTTP协议中的持久连接。SSEHTML5标准协议中的一部分。

WebSocket

WebSocket是一种在web应用中实现同步、双向、实时通信的技术。WebSocket基于HTTP以外的协议(ws协议),因此它可能需要额外的网络基础设施设置(代理服务器、NAT、防火墙等)。然而,WebSocket可以提供使用基于HTTP的技术难以实现的性能。

SSE网络协议

去订阅一个服务端推送事件,客户端需要发起一个GET请求,并设置如下的请求头:

  • Accept: text/event-stream 指明MediaType是事件流
  • Cache-Control: no-cache 不要对事件进行缓存
  • Connection: keep-alive 长连接
GET /sse HTTP/1.1
Accept: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

服务端需要提供包含以下响应头的response

  • Content-Type: text/event-stream;charset=UTF-8 告诉客户端响应是一个事件流
  • Transfer-Encoding: chunked 告诉客户端内容大小未知,为流传输
HTTP/1.1 200
Content-Type: text/event-stream;charset=UTF-8
Transfer-Encoding: chunked

建立连接之后,服务端会在消息可用时立即发送消息。事件是UTF-8编码的文本消息。事件与事件之间由两个换行符分隔\n \n。每个事件由一个或多个name:value字段组成,用一个换行符分隔\n。数据主要在data字段中传输,一个标准的事件格式如下:

id:1
event:删除音乐
data:具体的信息

id:2
event:添加音乐
data:具体的信息

在一次事件消息发送过程中,可以有多个data行,比如:

data:第一个事件的第一部分信息
data:第一个事件的第二部分信息

id event data是比较常见的字段,除此之外,还常用到的是retry字段。在retry字段中,服务端告知客户端超时重连的时间间隔(单位是毫秒)。如果连接断开,浏览器会在达到超时时间后重新连接。如果未指定此字段,默认超时重连的时间是3秒。

另外一个常见的字段是:value,即只有value没有name。它可以用来服务端向客户端发送注释,也可以用来保持连接,以防长连接超时断开。

: heartbeat

SSE客户端处理

为了建立一个SSE连接,客户端应该创建一个EventSource对象。

var eventSource = new EventSource('/sse');

也可以在建立连接时传递参数给服务端。

var eventSource = new EventSource('/sse?event=type1'); 

关闭一个SSE连接

eventSource.close();

连接有三种状态:

  • EventSource.CONNECTING = 0 正在连接
  • EventSource.OPEN = 1 连接已建立
  • EventSource.CLOSED = 2连接已关闭

eventSource的常用事件有以下几种:

//连接建立事件
eventSource.onopen = function () {
console.log('connection is established');
};
//连接发生错误
eventSource.onerror = function (event) {
console.log('connection state: ' + eventSource.readyState + ', error: ' + event);
};
//接收到消息
eventSource.onmessage = function (event) {
console.log('id: ' + event.lastEventId + ', data: ' + event.data);
};

对于消息的处理,一般会通过增加事件监听来进行。可以针对不同的event.type设置不同的处理逻辑。

eventSource.addEventListener('DELETE_SONG', function (event) {
//你的业务逻辑;
}, false);

EventSource对象已经被绝大部分现代浏览器支持,具体支持的浏览器,可以翻阅Can I Use网站。