最近嘗試了一下服務器端的推送,以前的作法都是客戶端輪詢,定時向服務器發送請求。但這形成了個人一些困擾:php
1:輪詢是由客戶端發起的,那麼在服務端就不能判別我要推送的內容是否已通過期,由於我很難判斷某個信息是否已經推送給所有的客戶端,那麼服務端就須要緩存大量的數據。若是數據保存在數據庫,那麼還要每次請求都須要查詢數據庫,這對數據庫和系統設計都是一個很大的挑戰。html
2:請求的頻率過高,每次的請求包中含有一樣的數據,這對pc來講也許算不得什麼,可是對於移動客戶端來說,這應該不是最佳的方案。尤爲是遇到還要作權限判斷的時候,那麼服務端的邏輯和效率也會形成用戶體驗的下降。html5
好在Html5爲咱們提供了一種方式:Server-Sent Events包含新的HTML元素EventSource和新的MIME類型 text/event-stream來完成個人須要。web
由於是第一次接觸Html5,w3school中也有對EventSource的說明和使用。因而立刻開始着手實踐。數據庫
頁面腳本就不用說了,按照w3school的方式便可。緩存
var source=new EventSource("demo_sse.php"); source.onmessage=function(event) { document.getElementById("result").innerHTML+=event.data + "<br />"; };
服務端的代碼也是如初一折,w3school提供了php和asp的代碼:服務器
//php方式 <?php header('Content-Type: text/event-stream'); header('Cache-Control: no-cache'); $time = date('r'); echo "data: The server time is: {$time}\n\n"; flush(); ?> //asp方式 <% Response.ContentType="text/event-stream" Response.Expires=-1 Response.Write("data: " & now()) Response.Flush() %>
//代碼解釋:mvc
也許你們應該注意到,php和asp的案例有一點不同,就是php推送的信息一個使用了"\n\n"做爲結束標誌,而asp卻沒有。而本人實踐則是用asp.net+mvc,通過測試,若是不以"\n\n"做爲結束標誌,那麼客戶端將不能接收到推送的值。還有須要特別聲明一下:推送的信息格式必須爲」data:內容\n\n「,不然。。。asp.net
public void Subscribe() { HttpContext.Response.ContentType = "text/event-stream"; HttpContext.Response.CacheControl = "no-cache"; HttpContext.Response.Write("data:" + DateTime.Now.ToString()+ "\n\n"); HttpContext.Response.Flush(); }
至此,客戶端應該能夠收到服務端推送的值。而如此簡單的結構真的能夠完成咱們須要的功能設計嗎?
此例咱們只是推送了一個當前時間,而咱們實際要推送的值是不斷變化的,否則也就沒有推送的必要了。測試
因而我想到了將訂閱的請求保存起來,當須要推送的時候,在對每一個請求進行循環推送,因而有了下面的代碼:
public class PublishService { private static IDictionary<string, HttpResponseBase> contexts = new Dictionary<string, HttpResponseBase>(); public static void AddHttpContext(HttpContextBase context) { var token = context.GetToken(」CookieName「); if (!contexts.Keys.Contains(token)) contexts.Add(token, context.Response); } private static void Publish() { foreach (var context in contexts.Values) { context.ContentType = "text/event-stream"; context.CacheControl = "no-cache"; msg = GetData(context.GetToken("CookieName")); context.Write("data:" + msg + "\n\n"); context.Flush(); } }
public void Subscribe()
{
PublishService.AddHttpContext(HttpContext);
PublishService.Publish();
} }
但是在進行測試的時候Chrome告訴我:EventSource's response has a MIME type ("text/plain") that is not "text/event-stream". Aborting the connection.
而FF告訴我:Firefox 沒法創建到 http://localhost:8000/Location/Notification/Subscribe 服務器的鏈接。
通過調試發現,在每次flush的時候發生異常:Server cannot flush a completed response.這到底是爲啥呢?不管是google,仍是baidu,我都沒能找到合適的答案,因此此案至今未結,如哪位知道請細說一二。
因而乎,我放棄了這種方式,轉而就推送一個時間看看是什麼效果。結果發現Chrome每隔3秒向客戶端推送一次,而FF是每5秒推送一次。有了這樣一個發現,那麼服務端的設計就應該是另外一個樣子:
public void Subscribe() { var data = GetData(); HttpContext.Response.ContentType = "text/event-stream"; HttpContext.Response.CacheControl = "no-cache"; HttpContext.Response.Write("data:" + data + "\n\n"); HttpContext.Response.Flush(); }
服務端只須要提供一個服務GetData(),這個服務用來獲取咱們須要推送的信息,而根據Server-Sent Events規範推薦若是沒有其餘的數據要發送,那麼按期的發送keep-alive註釋。其餘的事情就不用咱們操心了。
這只是一個簡單的使用,由於本人在使用EventSource的時候走了一些彎路,因此寫出來,但願能對你們有些幫助。
總結:走了一些彎路的緣由是起初對Server-Sent Events機制的不清楚。我的理解:該機制依然並不是由服務器端發起,而是還由客戶端向服務端定時發送請求,EventSource只是提供了一個很好的封裝,不用本身去維護而已。
後記:
求教:EventSource.onopen和EventSource.onerror每次都會觸發這兩個事件,並且每次獲得的結果都同樣,爲什麼?