傳統的WEB應用程式通訊時的簡單時序圖:
#現在Web App中,大都有Ajax,是這樣子:
HTML5有一個Server-Sent Events(SSE)功能,允許服務端推送資料到客戶端。 (通常叫資料推送)。基於資料推送是這樣的,當資料來源有新數據,它馬上發送到客戶端,不需要等待客戶端請求。這些新數據可能是最新聞,最新股票行情,來自朋友的聊天訊息,天氣預報等。
資料拉與推的功能是一樣的,使用者拿到新資料。但數據推送有一些優勢。 你可能聽過Comet, Ajax推送, 反向Ajax, HTTP流,WebSockets與SSE是不同的技術。可能最大的優勢是低延遲。 SSE用於web應用程式刷新數據,不需要使用者做任何動作。
你可能聽過HTML5的WebSockets,也能推送資料到客戶端。 WebSockets是實現服務端更加複雜的技術,但它是真的全雙工socket, 服務端能推送資料到客戶端,客戶端也能推送資料回服務端。 SSE工作於存在HTTP/HTTPS協議,支援代理伺服器與認證技術。 SSE是文字協定你能輕易的調試它。如果你需要發送大部二進位資料從服務端到客戶端,WebSocket是更好的選擇。關於SSE與WebSocket的差別,本文下面會講到。
HTML5 伺服器發送事件(server-sent event)允許網頁獲得來自伺服器的更新
Server-Sent 事件- 單一向訊息傳遞
Server-Sent 事件指的是網頁自動取得來自伺服器的更新。
以前也可能做到這一點,前提是網頁必須詢問是否有可用的更新。透過伺服器發送事件,更新能夠自動到達。
範例:Facebook/Twitter 更新、估價更新、新的部落格文章、賽事結果等。
瀏覽器支援(所有主流瀏覽器均支援伺服器傳送事件,除了Internet Explorer。)
EventSource 推送(ajax普通輪詢):
處理過程:
客戶端建立EventSource物件,對伺服器透過http協定不斷進行請求。伺服器對客戶端的回應資料格式有四個部分構成,event,data,id,空格行。用戶端接收到伺服器端的回應資料之後,根據event事件值,找到EventSource物件對應的事件監聽器。
接收Server-Sent 事件通知
EventSource 物件用於接收伺服器傳送事件通知:
##
//创建一个新的 EventSource 对象,规定发送更新的页面的 URL var source = new EventSource("../api/MyAPI/ServerSentEvents"); //默认支持message事件 source.onmessage = function (event) { console.log(source.readyState); console.log(event); };
建立一個新的EventSource 對象,然後規定發送更新的頁面的URL(本例中是"demo_sse.php"),
參數url就是伺服器網址,必須與目前網頁的網址在同一個網域(domain),而且協定和連接埠都必須相同。 每接收到一次更新,就會發生onmessage 事件
以下實例,我們編寫了一段額外的程式碼來偵測伺服器傳送事件的瀏覽器支援情況:
if(!!EventSource && typeof(EventSource)!=="undefined") { // 浏览器支持 Server-Sent // 一些代码.....}else{ // 浏览器不支持 Server-Sent..}
為了讓上面的範例可以運行,您還需要能夠發送資料更新的伺服器(例如PHP、
ASP、ASP.NET、Java)。 伺服器端事件流的語法是非常簡單的。你
需要把 "Content-Type" 標頭設定為 "text/event-stream"。現在,您可以開始發送事件流了。 我只會
C#,所以用ASP.NET的MVC 裡面的ApiController寫了個最簡單的伺服器端:
public class MyAPIController : ApiController { /// <summary> /// ...api/MyAPI/ServerSentEvents /// </summary> /// <returns></returns> [HttpGet, HttpPost] public Task<HttpResponseMessage> ServerSentEvents() { //Response.ContentType = "text/event-stream" //Response.Expires = -1 //Response.Write("data: " & now()) //Response.Flush() string data = "id: 123456\nevent: message\ndata: 666\n\n"; HttpResponseMessage response = new HttpResponseMessage { //注意:ContentType = "text/event-stream" Content = new StringContent(data, Encoding.GetEncoding("UTF-8"), "text/event-stream") }; return Task.FromResult(response); } }
程式碼解釋:
把標頭"Content-Type" 設為"text/event-stream"
規定不對頁面進行快取
# 輸出發送日期(始終以"data : " 開頭)
向網頁刷新輸出資料
EventSource 物件
新產生的EventSource實例對象,有一個readyState屬性,表示連接所處的狀態。
source.readyState
它可以取下列值:
0,相當於常數EventSource.CONNECTING,表示連線尚未建立,或連接斷線。
1,相當於常數EventSource.OPEN,表示連線已經建立,可以接受資料。
2,相當於常數EventSource.CLOSED,表示連接已斷,且不會重連。
在上面的範例中,我們使用 onmessage 事件來取得訊息。不過還可以使用其他事件:
事件描述
onopen 當通往伺服器的連線被開啟
onmessage
當發生錯誤
#open事件
連接一旦建立,就會觸發open事件,可以定義對應的
回呼函數。
source.onopen = function(event) {
// han
#dl
e open event
source.addEvent
List
ener("open", function(event) {
// handle open event
}, false);
message事件
收到到數據就會觸發message事件。
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);
參數物件event有以下屬性:
#data:伺服器端傳回的資料(文字格式)。
origin: 伺服器端URL的網域部分,即協定、網域名稱和連接埠。
lastEventId:資料的編號,由伺服器端傳送。如果沒有編號,這個屬性為空。
error事件
如果發生通訊錯誤(例如連線中斷),就會觸發error事件。
source.onerror = function(event) {
// handle error event};
// 或者source.addEventListener("error" , function(event) { // handle error event}, false);
自訂事件
source.addEventListener("foo", function(event) { var data = event.data;
var origin = event.origin; var lastEventId = event.lastEventId; // handle message}, false);上面程式碼表示,瀏覽器對foo事件進行監聽。
close方法close方法用於關閉連線。 source.close()
;資料格式概述
伺服器端所傳送的資料的
如下:
Content-Type: text/event-stream
- Control: no-cache
######Connection: keep-alive#########後面的行都是以下格式:######field: value\n###field可以取四個值:“data”, “event”, “id”, 或 “retry”,也就是說有四類頭資訊。每次HTTP通訊可以包含這四類頭資訊中的一類或多類。 \n代表換行符號。 ######以冒號開頭的行,表示###註解###。通常,伺服器每隔一段時間就會向瀏覽器發送一個註釋,保持連線不中斷。 ######: This is a comment###下面是一些範例。 ######: this is a test stream\n\n######data: some text\n\n###data: another message\n
data: with two lines \n\n
data:数据栏
数据内容用data表示,可以占用一行或多行。如果数据只有一行,则像下面这样,以“\n\n”结尾。
data: message\n\n
如果数据有多行,则最后一行用“\n\n”结尾,前面行都用“\n”结尾。
data: begin message\n
data: continue message\n\n
总之,最后一行的data,结尾要用两个换行符号,表示数据结束。
以发送JSON格式的数据为例。
data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n
id:数据标识符
数据标识符用id表示,相当于每一条数据的编号。
id: msg1\n
data: message\n\n
浏览器用lastEventId属性读取这个值。一旦连接断线,浏览器会发送一个HTTP头,里面包含一个特殊的“Last-Event-ID”头信息,将这个值发送回来,用来帮助服务器端重建连接。因此,这个头信息可以被视为一种同步机制。
event栏:自定义信息类型
event头信息表示自定义的数据类型,或者说数据的名字。
event: foo\n
data: a foo event\n\n
data: an unnamed event\n\n
event: bar\n
data: a bar event\n\n
上面的代码创造了三条信息。第一条是foo,触发浏览器端的foo事件;第二条未取名,表示默认类型,触发浏览器端的message事件;第三条是bar,触发浏览器端的bar事件。
retry:最大间隔时间
浏览器默认的是,如果服务器端三秒内没有发送任何信息,则开始重连。服务器端可以用retry头信息,指定通信的最大间隔时间。
retry: 10000\n
--------------------------------------------------------------------------------------
规范
Server-sent Events 规范是 HTML 5 规范的一个组成部分,具体的规范文档见参考资源。该规范比较简单,主要由两个部分组成:第一个部分是服务器端与浏览器端之间的通讯协议,第二部分则是在浏览器端可供 JavaScript 使用的 EventSource 对象。通讯协议是基于纯文本的简单协议。服务器端的响应的内容类型是“text/event-stream”。响应文本的内容可以看成是一个事件流,由不同的事件所组成。每个事件由类型和数据两部分组成,同时每个事件可以有一个可选的标识符。不同事件的内容之间通过仅包含回车符和换行符的空行(“\r\n”)来分隔。每个事件的数据可能由多行组成。代码清单 1 给出了服务器端响应的示例:
retry: 10000\n event: message\n id: 636307190866448426\n data: 2017/05/18 15:44:46\n\n
响应报文头部:
响应报文内容:
每个事件之间通过空行来分隔。对于每一行来说,冒号(“:”)前面表示的是该行的类型,冒号后面则是对应的值。可能的类型包括:
类型为空白,表示该行是注释,会在处理时被忽略。
类型为 data,表示该行包含的是数据。以 data 开头的行可以出现多次。所有这些行都是该事件的数据。
类型为 event,表示该行用来声明事件的类型。浏览器在收到数据时,会产生对应类型的事件。
类型为 id,表示该行用来声明事件的标识符。
类型为 retry,表示该行用来声明浏览器在连接断开之后进行再次连接之前的等待时间。
当有多行数据时,实际的数据由每行数据以换行符连接而成。
如果服务器端返回的数据中包含了事件的标识符,浏览器会记录最近一次接收到的事件的标识符。如果与服务器端的连接中断,当浏览器端再次进行连接时,会通过 HTTP 头“Last-Event-ID”来声明最后一次接收到的事件的标识符。服务器端可以通过浏览器端发送的事件标识符来确定从哪个事件开始来继续连接。
对于服务器端返回的响应,浏览器端需要在 JavaScript 中使用 EventSource 对象来进行处理。EventSource 使用的是标准的事件监听器方式,只需要在对象上添加相应的事件处理方法即可。EventSource 提供了三个标准事件:
EventSource 对象提供的标准事件
名称 说明 事件处理方法
open 当成功与服务器建立连接时产生 onopen
message 当收到服务器发送的事件时产生 onmessage
error 当出现错误时产生 onerror
而且,服务器端可以返回自定义类型的事件。对于这些事件,可以使用 addEventListener 方法来添加相应的事件处理方法:
var es = new EventSource('events'); es.onmessage = function(e) { console.log(e.data); };//自定义事件 myeventes.addEventListener('myevent', function(e) { console.log(e.data); });
在指定 URL 创建出 EventSource 对象之后,可以通过 onmessage 和 addEventListener 方法来添加事件处理方法。当服务器端有新的事件产生,相应的事件处理方法会被调用。EventSource 对象的 onmessage 属性的作用类似于 addEventListener( ‘ message ’ ),不过 onmessage 属性只支持一个事件处理方法。
传统的网页都是浏览器向服务器“查询”数据,但是很多场合,最有效的方式是服务器向浏览器“发送”数据。比如,每当收到新的电子邮件,服务器就向浏览器发送一个“通知”,这要比浏览器按时向服务器查询(polling)更有效率。服务器发送事件(Server-Sent Events,简称SSE)就是为了解决这个问题,而提出的一种新API,部署在EventSource对象上。目前,除了IE,其他主流浏览器都支持。
简单说,所谓SSE,就是浏览器向服务器发送一个HTTP请求,然后服务器不断单向地向浏览器推送“信息”(message)。这种信息在格式上很简单,就是“信息”加上前缀“data: ”,然后以“\n\n”结尾。
SSE与WebSocket有相似功能,都是用来建立浏览器与服务器之间的通信渠道。两者的区别在于:
WebSocket是全双工通道,可以双向通信,功能更强;SSE是单向通道,只能服务器向浏览器端发送。
WebSocket是一个新的协议,需要服务器端支持;SSE则是部署在HTTP协议之上的,现有的服务器软件都支持。
SSE是一个轻量级协议,相对简单;WebSocket是一种较重的协议,相对复杂。
SSE默认支持断线重连,WebSocket则需要额外部署。
SSE支持自定义发送的数据类型。
从上面的比较可以看出,两者各有特点,适合不同的场合。
个人完整的HTML5页面和C#(MVC实现服务端代码)如下:
前端HTML5页面:
<!DOCTYPE html><html><head> <meta charset="utf-8"> <title>HTML5 服务器发送事件(Server-Sent Events)-单向消息传递</title> <meta name="author" content="熊仔其人" /> <meta name="generator" content="2017-05-18" /></head><body> <h1>获取服务端更新数据</h1> <p id="result"></p><script>if(typeof(EventSource)!=="undefined") { //创建一个新的 EventSource 对象,规定发送更新的页面的 URL var source = new EventSource("../api/MyAPI/ServerSentEvents"); //默认支持open事件 source.onopen = function (event) { console.log(source.readyState); console.log(event); }; //默认支持error事件 source.onerror = function (event) { console.log(source.readyState); console.log(event); }; //默认支持message事件 source.onmessage = function (event) { console.log(source.readyState); console.log(event); document.getElementById("result").innerHTML += event.data + "<br>"; }; //处理服务器响应报文中的自定义事件 source.addEventListener("CustomEvent", function (e) { console.log("唤醒自定义事件"); console.log(e); document.getElementById("result").innerHTML += e.data + "<br>"; }); }else{ document.getElementById("result").innerHTML="抱歉,你的浏览器不支持 server-sent 事件..."; }</script></body></html>
C#写的服务器端:
using System;using System.Net.Http;using System.Text;using System.Threading.Tasks;using System.Web.Http;namespace WebTest.Controllers { /// <summary> /// api/{controller}/{id} /// </summary> public class MyAPIController : ApiController { static readonly Random random = new Random(); /// <summary> /// ...api/MyAPI/ServerSentEvents /// </summary> /// <returns></returns> [HttpGet, HttpPost] public Task<HttpResponseMessage> ServerSentEvents() { //Response.ContentType = "text/event-stream" //Response.Expires = -1 //Response.Write("data: " & now()) //Response.Flush() string data = ""; if (random.Next(0, 10) % 3 == 0) { //唤醒自定义的CustomEvent data = ServerSentEventData("这是自定义通知", DateTime.Now.Ticks.ToString(), "CustomEvent"); } else { //唤醒默认的message data = ServerSentEventData(DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss"), DateTime.Now.Ticks.ToString()); } HttpResponseMessage response = new HttpResponseMessage { //注意:ContentType = "text/event-stream" Content = new StringContent(data, Encoding.GetEncoding("UTF-8"), "text/event-stream") }; return Task.FromResult(response); } public string ServerSentEventData(string data, string id, string _event = "message", long retry = 10000) { StringBuilder sb = new StringBuilder(); sb.AppendFormat("retry:{0}\n", retry); sb.AppendFormat("event:{0}\n", _event); sb.AppendFormat("id:{0}\n", id); sb.AppendFormat("data:{0}\n\n", data); return sb.ToString(); } } }
通信在页面上的显示结果:
通过Chrome监控网络交互时序:
通过Chrome浏览器控制台输出,下面是一轮ope、message、error事件的详情:
至此,大功告成。
以上是HTML5支援伺服器發送事件的詳細內容。更多資訊請關注PHP中文網其他相關文章!