HTML5服務器推送消息的各類解決辦法

 

摘要

在各類BS架構的應用程序中,每每都但願服務端可以主動地向客戶端推送各類消息,以達到相似於郵件、消息、待辦事項等通知。html

往BS架構自己存在的問題就是,服務器一直採用的是一問一答的機制。這就意味着若是客戶端不主動地向服務器發送消息,服務器就沒法得知如何給客戶端推送消息。ajax

隨着HTML、瀏覽器等各項技術、標準的發展,依次生成了不一樣的手段與方法可以實現服務端主動推送消息,它們分別是:AJAX,Comet,ServerSent以及WebSocket。算法

本篇文章將對上述說起到的各類技術手段進行直白化的解釋。編程


AJAX

正常的一個頁面在瀏覽器中是這樣工做的:瀏覽器

  1. 用戶向給予瀏覽器一個須要訪問的地址
  2. 瀏覽器根據這個地址訪問服務器,並與服務器之間建立一個TCP鏈接(HTTP請求)
  3. 服務器根據這個地址和一些其它數據,組建一段HTML文本,將寫入TCP鏈接,而後關閉鏈接
  4. 瀏覽器獲得了來自服務器的HTML文本,解析並呈現了瀏覽器上給用戶瀏覽

此時,用戶點擊了網站上任何一個<a>或觸發任何一個<form>提交時:服務器

  1. 瀏覽器根據form的參數或者a的參數,做爲訪問的地址
  2. 與服務器建立TCP鏈接
  3. 服務器組建HTML文本,而後關閉鏈接
  4. 瀏覽器將當前顯示的頁面摧毀,並按照新的HTML文本呈現一個新的頁面給用戶

咱們不難發現的是整個過程當中間,一旦創建了一個鏈接,頁面就沒法再維護住了。整個過程看上去有點強買強賣,也許我只要一杯新的可樂,但你非要給我一整個套餐組合。架構

此時咱們能夠了解一下XmlHttpRequest組件,這個組件提供咱們手動建立一個HTTP請求,發送咱們想要的數據,服務器也能夠只返回咱們想要的結果,最大的好處是,當咱們收到服務器的響應時,原來的頁面沒有被摧毀。這就比如,我喊一句"個人咖啡喝完了,我要續杯",而後服務員就拿了一杯咖啡過來,而不是會把我沒吃完的套餐所有倒掉。框架

當咱們利用AJAX實現服務器推送時,其實質是客戶端不停地向服務器詢問"有沒有給個人消息呀?",而後服務器回答"有"或"沒有"來達到的實現效果。它的實現方法也很簡單,利用jQuery框架封裝好的AJAX調用也很方便:函數

function getMessage(fn) {
    $.ajax({
        url: "Handler.ashx", //一個可以提供消息的頁面
        dataType: "text",    //響應類型,能夠是JSON,XML等其它類型
        type: "get",         //HTTP請求類型,還能夠是post
        success: function (d, s) {
            fn(d);           //獲得了正常的響應時,利用回調函數通知外部
        },
        complete: function (x, s) {
            setTimeout(function () {
                getMessage(fn);
            }, 5000);       //不管響應成功或失敗,在若干秒後再詢問一次服務器
        }
    });
}

 經過上面的代碼,能夠每隔5秒詢問一次服務器是否有須要處理的消息,經過這種方式能夠達到推送的效果,可是會存在一個問題:post

  1. 間隔時間越快,推送的及時性越好,服務器的消費越大;
  2. 間隔時間越慢,推送的及時性越低,服務器的消費越小。

並且嚴格地來講,這種實際方式,並非真正意義上的服務器主動推送消息,但因爲早期技術手段缺少,因此AJAX輪循成爲了一種很廣泛的手段。

 


 

 

Comet

咱們知道HTTP請求實際上是基於TCP鏈接實現的,再看看以前說的HTTP請求處理過程:

  1. 客戶端與服務器創建TCP鏈接
  2. 服務器根據客戶端提交的報文處理並生成HTML文本
  3. 將HTML封閉成爲HTTP協議報文並返回給客戶端
  4. 關閉連接。

看到這個處理過程,咱們不難聯想到,若是把第4步——關閉鏈接給省掉,那不就至關於有一個長鏈接一直被維持住了麼。經過對服務端的一些操做,咱們能夠直接將數據從這個TCP鏈接發送客戶端了。

經過這種技術,咱們能夠大大提升服務器推送的實時性,還能夠減去服務端不停地創建、施放鏈接所造成的開銷。

目前市面上有很多基於AJAX實現的Comet機制,但主要有兩種方式:

  1. 創建鏈接後依然使用"詢問"+"應答"的模式。雖然工做方式沒變,可是由於減去了每次創建與施放鏈接的工做,因此性能上提高了不少。並且服務器對TCP鏈接能夠有上下文的定義,而不像之前的AJAX徹底是無狀態的。
  2. 經過對Stream的寫入實現服務器將數據主動發送到客戶端。由於是TCP鏈接,因此經過對服務器的編程,咱們能夠主動的把數據從服務端發送給客戶端,從模式上真正創建起了推送的概念。

 


 

 

Server-Sent

Server-Sent是HTML5提出一個標準,它延用了Comet的思路,並對其進行了一些規範。使得Comet這項技術由原來的分支衍生技術轉成了正統的官方標準。

它的原理與Comet相同,由客戶端發起與服務器之間建立TCP鏈接,而後並維持這個鏈接,至到客戶端或服務器中的作任何一放斷開,ServerSent使用的是"問"+"答"的機制,鏈接建立後瀏覽器會週期性地發送消息至服務器詢問,是否有本身的消息。

這項標準不只要求了支持的瀏覽器可以原生態的建立與服務器的長鏈接,更要求了對JavaScript腳本的統一性,使得兼程該功能的瀏覽器可使用同一套代碼完成Server-Sent的編碼工做。

建立代碼很是簡單:

//定義一個ServerSent對象
var s = new EventSource("Handler.ashx");
//當收到一個非自定義事件時的回調函數
s.onmessage = function (e) {
    alert(e.data);
};
//當收到一個被服務器命名爲MyEvent事件消息時的回調函數
s.addEventListener("MyEvent", function (e) {
    alert(e.data);
});

而服務器的代碼也很簡單:

public class Handler : IHttpHandler
{

    public void ProcessRequest(HttpContext context)
    {
        context.Response.ContentType = "text/event-stream";
        context.Response.Expires = -1;
        context.Response.Write("event: MyEvent\r\n");       //事件類型,使用\r\n結尾
        context.Response.Write("data: HelloWorld!\r\n");    //事件數據,換行時使用\r\n,並在新行再加上data:
        context.Response.Write("data: I'm server!\n\n");    //事件數據結束,使用\n\n
        context.Response.Flush();                           //這裏不能用End,不然是關閉鏈接的
    }

    public bool IsReusable
    {
        get
        {
            return true;
        }
    }

}

兩小段代碼,就已經具有了服務器消息推送了。

總得來講SeverSent就是HTML5規範下的Comet,具備更好的統一性,並且簡單好用。

 

 


 

 

WebSocket

看名字就知道了,這是一個能夠用在瀏覽器上的Socket鏈接。

這也是一個HTML5標準中的一項內容,他要求瀏覽器能夠經過JavaScript腳本手動建立一個TCP鏈接與服務端進行通信。

WebSocket不包含太多的額外功能,僅僅就是TCP鏈接的幾項基本功能:創建,臨時以及發送。

另外WebSocket使用了ws和wss協議,須要服務器有與之握手的算法才能將鏈接打開。

因此WebSocket相對於以前幾種手段來講,其編碼量是最大的,但因爲沒有其它的約束,所以它也能夠自由地實現全部可能的功能。

便可以知足"問"+"答"的響應機制,也能夠實現主動推送的功能。

與ServerSent相同,HTML5也對WebSocket調用的JavaScript進行規範,咱們能夠弄過很簡單的一代碼構建一個WebSocket鏈接

var ws = new WebSocket("ws://192.168.0.105:10080"); //鏈接服務器        

ws.onopen = function (event) { alert("已經與服務器創建了鏈接\r\n當前鏈接狀態:" + this.readyState); };
ws.onmessage = function (event) { alert("接收到服務器發送的數據:\r\n" + event.data); };
ws.onclose = function (event) { alert("已經與服務器斷開鏈接\r\n當前鏈接狀態:" + this.readyState); };
ws.onerror = function (event) { alert("WebSocket異常!"); };

還能夠經過send的方式發送消息

ws.send("Hello World");

WebSocket具備較爲複雜的協議,須要在服務端作額外編程才能進行數據通信。有關協議的詳細內容,我會在之後的文章中進行解釋。

 

WebSocket + MessageQueue

MessageQueue,簡稱MQ,也就是消息列隊。是一種經常用於Tcp服務端的技術。經過生產和訪問各類消息類型,MQ服務器會將生產者所生成的消息發給感興趣的客戶端。市面上有不少的MQ框架,好比:ActiveMQ。

ActiveMQ已經支持了WebSocket協議,也就意味着,WebSocket已經能夠做爲一個生產者或一個消費者,與MQ服務器鏈接。

開發者能夠經過MQTT的JS腳本,鏈接上MQ服務器,同時將Web服務器也連上MQ服務器,今後能夠告別了Http通信協議,完徹底全使用Socket通信來完成數據的交換。

 


 

總結:

總得來講,在HTML5規範下,最推薦使用ServerSent和WebSocket的方式進行服務器消息的推送。

對比這兩種方式。

ServerSent的方式,可使服務端的開發依然依用之前的方式,可是其工做方式與Comet相似。

而WebSocket的方式,則對服務端的開發有着較高的要求,但其工做方式是徹底的推送。

我本人其實挺偏向WebSocket + MQ的工做方式,可是對於老項目的翻新,仍是用SeverSent比較好

 


結尾

本文爲做者原創,轉載請註明出處:http://www.cnblogs.com/ShimizuShiori/p/5464063.html

文章中的相關代碼能夠在 http://j.zizhusoft.com/Develop/Explorer.aspx 中的ServerSent目錄中查看

相關文章
相關標籤/搜索