HTML5 服务器发送事件(SSE)

最后更新:
阅读次数:

推送技术(Push technology),又名反向 AJAX,指的是一种基于 Internet,将由中心或发布者发出消息传输给用户的技术。

  • 常见用途: 订阅消息定时推送,体育比赛分数实时更新,股票报价实时更新等。

实现推送技术的常见方法

  • 短轮询: 即浏览器定时向服务器发送请求,看看有没有需要更新的数据
  • 长轮询: 浏览器向服务器发送一个请求,然后服务器一直保持连接打开,直到有数据可发送。服务器发送完数据后,浏览器关闭连接。接着浏览器立马又发起一个到服务器的新请求。如此重复
  • HTTP 流: 浏览器向服务器发送一个请求,然后服务器一直保持连接打开,然后周期性地向浏览器发送数据

服务器发送事件

服务器发送事件(Server-Sent Events,简称 SSE) 用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。SSE 的出现是为了简化上面推送方法的实现方式。

简单说,SSE 就是浏览器向服务器发送一个 HTTP 请求,建立连接后,服务器不断单向地向浏览器推送信息(message)。

Event Source 接口

EventSource 接口用来接收服务器发送事件。它通过 HTTP 连接到一个服务器,并且在不关闭连接的情况下以 text/event-stream 格式接收事件

  • new EventSource(url [,configuration])
    • url:代表远程资源的位置(资源 URL 必须遵守同源策略)
    • configuration:为配置新连接提供选项,可选项是 withCredentials,默认为 false
  • readyState 属性: 表示当前连接状态(0 — connecting,1 — open,2 — closed)
  • url 属性: 表示请求的 URL
  • open 事件: 在建立连接时触发
  • message 事件: 在从服务器收到新事件时触发
  • error 事件: 在无法建立连接时触发
  • close() 方法: 强制立即断开连接并且不再自动重新连接

默认情况下,EventSource 对象会保持与服务器的活动连接。如果连接断开,还会重新连接。这就意味着,SSE 适合实现长轮询HTTP 流

服务器发送数据的数据格式

与使用 XMLHttpRequest 不同,服务器发送事件不允许服务器随意发送数据,而是必须遵守一个简单明确的数据格式。

服务器发送的每条消息是由单个或多个字段组成的,每个字段由字段名,一个冒号,以及字段值组成。

  • 四个字段名
    • data: 消息的数据字段
    • id: 事件 ID,会成为当前 EventSource 对象的内部属性最后一个事件 IDevent.lastEventId)的属性值
    • event: 指定客户端在接收到该消息后应该触发的事件的事件名称(可以是自定义事件)。若没有消息中没有该字段,默认触发 message 事件
    • retry: 一个整数,指定了重新连接的时间(单位为毫秒)

除了上面规定的字段名,其他所有的字段名都会被忽略。如果一行文本中不包含冒号,则整行文本会被解析成为字段名,其字段值为空。

两种情况会导致浏览器重新发起连接:一种是浏览器开始正常接收完服务器发来的信息二是由于网络错误等原因,导致连接出错

  • 在第一种情况下,浏览器会立即重新连接
  • 在第二种情况下,浏览器自动重新连接的默认等待时间是 3 秒
  • retry 字段只会影响第二种情况,不会影响第一种情况(下面有测试)

由于服务器每次向客户端发送的数据之间需要有一个空行,所以需要在每次发送的数据末尾加上 \n\n,从而让每次发送的消息之间都有空行。(否则不能正常显示消息)

// 一些服务器发送的数据的例子 (php举例)

// 发送单行数据
echo "data: A message from server\n\n";

// 发送多行数据,比如 json 数据
echo "data: {\n";
echo "data: "name": "percy"\n";
echo "data: "age: 21"\n";
echo "data: }\n\n";

// 发送带有事件 ID 的信息
echo "data: I has ID number\n";
echo "id: 222\n\n";

测试

为了方便,我使用了本地服务器(Wamp)进行了测试。

  • 客户端部署 SSE
// 下面代码在浏览器控制端运行
// 当前路径:http://localhost/sse/index.html

function sseTest(url) {
var sse = new EventSource(url);

// 建立连接时触发
sse.onopen = function() {
console.log("连接已建立~");
};

// 处理从服务器收到的数据
sse.onmessage = function(event) {
console.log("从服务器接收到的数据:" + event.data);
};

// 连接中断时触发
sse.onerror = function() {
console.log("连接已中断,请稍后,我可能会重连哦~");
};
}

sseTest("./sse.php");
  • 服务器部署 SSE
// sse.php
// 我的路径:http://localhost/sse/sse.php

<?php
// 设置 MIME 类型
header('Content-Type: text/event-stream');
// 关闭 Web 缓存,否则消息可能不按先后次序到达
header('Cache-Control: no-cache');

// $n 用来在循环中每次选择发送不同的消息
$n = 1;

// 关闭 php 内置的缓冲机制,这样 php 返回的数据会立即传给浏览器
ob_end_clean();

// 大循环,按 $n 的值依次发送消息
while(true){
switch($n){
case 1:
echo "data: 我是一条单行信息~\n\n";
break;
case 2:
echo "data: 我是一段多行信息~\n";
echo "data: {\ndata: \"name\": \"percy\"\ndata: \"age: 21\"\ndata: }\n\n";
break;
case 3:
echo "data: 我是带有 ID 的信息(ID:250)~\n";
echo "id: 250\n\n";
break;
case 4:
echo "data: 我设置了自定义事件,但没有设置 ID ~\n";
echo "event: sseEvent\n\n";
break;
case 5:
echo "data: 我设置了自定义事件和 ID (ID:521)~\n";
echo "event: sseEvent\n\n";
break;
case 6:
echo "data: 我是另一条单行信息~\n\n";
break;
case 7:
echo "data: 我定义了 retry 属性(10秒)~\n";
echo "data: 当前时间:".date("h:m:s")."\n";
echo "retry: 10000\n\n";
break;
case 8:
echo "data: 我为了验证 retry 属性~\n";
echo "data: 当前时间:".date("h:m:s")."\n\n";
break;
default:
break;
}

// 使用 flush() 的用意是立即发送数据,而不是先缓存起来,等 php 代码执行完毕后再发送
flush();

// 程序暂停 5 秒,达到定时发送的效果
sleep(5);

// 若发送的数据时最后一条消息,则重置 $n,重新开始循环
if($n == 8){
$n = 0;
}else{
$n++;
}
}
?>

SSE 与 Web Sockets 的区别

  • SSE 是单向连接,只能服务器向客户端发送数据,而 Web Sockets 是双向的
  • SSE 是部署在 HTTP 协议上的,而 Web Sockets 是一种新的协议,需要服务器支持。
  • SSE 默认支持断线重连,而 Web Sockets 不会重连

面对某个具体的用例,需要用 SSE 还是 Web Sockets?请按照上面两者之间的区别具体进行分析。比如:需要在页面上实时更新体育比赛数据时,用 SSE,而如果是一个聊天室,则用 Web Sockets。