最近有個項目,其中有項需求要從服務器端主動向客戶端推送數據,本覺得很簡單,但在實際作的過程當中發現很棘手,並無想象中的簡單。從網上搜索學習,發現主流講的仍是Ajax的長輪詢技術或者流技術,websocket還多多少少存在些弊端。爲了便於記憶,也爲了對跟我同樣的菜鳥提供些幫助和參考,把學習過程當中學習拜讀的文章和體會記錄下來,以備之後查驗和溫習。大多數內容屬於網絡整理,也有一部分屬於實踐的體會,但願對你們有所幫助。javascript
隨着AJAX技術的興起,讓廣大開發人員又一次看到了使用瀏覽器來替代桌面應用的機會,而且此次機會很是大。AJAX將整個頁面的刷新變成頁面局部的刷新,而且數據的傳送是以異步方式進行,這使得網絡延遲帶來的視覺差別將會消失。AJAX還利用DHTML和豐富的JavasSript語言來模擬桌面系統的各類事件和響應過程,以及平滑滾動和拖拽的效果。還不止這些,更有一些IT巨頭(Google、Sun、Oracle等)提供了很是豐富的AJAX開發工具,使得開發和調試AJAX應用變得簡單高效,而且開發的AJAX應用還能夠跨越各類瀏覽器和操做系統。在這種狀況下基於AJAX的Web應用迅速涌起,吞噬着原有桌面系統的份額。聊天工具、郵件閱讀器、博客編輯器,甚至是Office辦公軟件和文字處理軟件在瀏覽器中都有着美麗的外觀和幾乎能夠與桌面系統媲美的交互界面。Google更是提出「有了瀏覽器和Google,就不須要微軟」的口號和策略。在Ajax的世界中,除了傳統的CAD設計軟件和大型遊戲軟件等由於對系統硬件的苛刻需求,還離不開桌面系統之外,彷佛其餘全部的應用均可以變成Web應用了。html
可是,在瀏覽器中的AJAX應用中存在一個致命的缺陷沒法知足傳統桌面系統的需求。那就是「服務器發起的消息傳遞(Server-Initiated Message Delivery)」。在不少的應用當中,服務器軟件須要向客戶端主動發送消息或信息。由於服務器掌握着系統的主要資源,可以最早得到系統的狀態變化和事件的發生。當這些變化發生的時候,服務器須要主動地向客戶端實時地發送消息。例如股票的變化。在傳統的桌面系統中,這種需求沒有任何問題,由於客戶端和服務器之間一般存在着持久的鏈接,這個鏈接能夠雙向傳遞各類數據。而基於HTTP協議的Web應用卻不行。在Web世界中,服務器永遠是被動地發送數據,前提是客戶端必須先發送請求。瀏覽器其實並不知道服務器的信息何時會有改變,爲了模擬實時的交流,或者不想錯過某些信息,只能經過輪詢(Polling)技術不斷刷新頁面來得到最新的數據。這種方式不但浪費服務器的資源,最重要的是每次創建(或關閉)新的HTTP鏈接都有必定的延遲,這種延遲使得頻繁信息傳遞的應用沒法忍受。因而就產生了「服務器推送技術」。java
「服務器推」是一種很早就存在的技術,之前在實現上主要是經過客戶端的套接口,或是服務器端的遠程調用。由於瀏覽器技術的發展比較緩慢,沒有爲「服務器推」的實現提供很好的支持,在純瀏覽器的應用中很難有一個完善的方案去實現「服務器推」並用於商業程序。最近幾年,由於 AJAX 技術的普及,以及把 IFrame 嵌在「htmlfile「的 ActiveX 組件中能夠解決 IE 的加載顯示問題,一些受歡迎的應用如 meebo,gmail+gtalk 在實現中使用了這些新技術;同時「服務器推」在現實應用中確實存在不少需求。由於這些緣由,基於純瀏覽器的「服務器推」技術開始受到較多關注,Alex Russell(Dojo Toolkit 的項目 Lead)稱這種基於 HTTP長鏈接、無須在瀏覽器端安裝插件的「服務器推」技術爲「Comet」。目前已經出現了一些成熟的 Comet 應用以及各類開源框架;一些 Web 服務器如 Jetty 也在爲支持大量併發的長鏈接進行了不少改進。關於 Comet 技術最新的發展情況請參考關於 Comet 的 wiki。web
下面介紹兩種 Comet 應用的實現模型。瀏覽器
1、基於 AJAX 的長輪詢(long-polling)方式服務器
AJAX 的出現使得 JavaScript 能夠調用 XMLHttpRequest 對象發出 HTTP 請求,JavaScript 響應處理函數根據服務器返回的信息對 HTML 頁面的顯示進行更新。使用 AJAX 實現「服務器推」與傳統的 AJAX 應用不一樣之處在於:websocket
服務器端會阻塞請求直到有數據傳遞或超時才返回。網絡
客戶端JavaScript 響應處理函數會在處理完服務器返回的信息後,再次發出請求,從新創建鏈接。併發
當客戶端處理接收的數據、從新創建鏈接時,服務器端可能有新的數據到達;這些信息會被服務器端保存直到客戶端從新創建鏈接,客戶端會一次把當前服務器端全部的信息取回。框架
一些應用及示例如 「Meebo」, 「Pushlet Chat」 都採用了這種長輪詢的方式。相對於「輪詢」(poll),這種長輪詢方式也能夠稱爲「拉」(pull)。由於這種方案基於 AJAX,具備如下一些優勢:請求異步發出;無須安裝插件;IE、Mozilla FireFox 都支持 AJAX。
在這種長輪詢方式下,客戶端是在 XMLHttpRequest 的 readystate 爲 4(即數據傳輸結束)時調用回調函數,進行信息處理。當 readystate 爲 4 時,數據傳輸結束,鏈接已經關閉。Mozilla Firefox 提供了對 Streaming AJAX 的支持, 即 readystate 爲 3 時(數據仍在傳輸中),客戶端能夠讀取數據,從而無須關閉鏈接,就能讀取處理服務器端返回的信息。IE 在 readystate 爲 3 時,不能讀取服務器返回的數據,目前 IE 不支持基於 Streaming AJAX。
2、基於 Iframe 及 htmlfile 的流(streaming)方式
iframe 是很早就存在的一種 HTML 標記, 經過在 HTML 頁面裏嵌入一個隱蔵幀,而後將這個隱蔵幀的 SRC 屬性設爲對一個長鏈接的請求,服務器端就能源源不斷地往客戶端輸入數據。
上節提到的 AJAX 方案是在 JavaScript 裏處理 XMLHttpRequest 從服務器取回的數據,而後 Javascript 能夠很方便的去控制 HTML 頁面的顯示。一樣的思路用在 iframe 方案的客戶端,iframe 服務器端並不返回直接顯示在頁面的數據,而是返回對客戶端 Javascript 函數的調用,如「<script type="text/javascript">js_func(「data from server 」)</script>」。服務器端將返回的數據做爲客戶端JavaScript 函數的參數傳遞;客戶端瀏覽器的 Javascript 引擎在收到服務器返回的 JavaScript 調用時就會去執行代碼。
這種方式每次數據傳送不會關閉鏈接,鏈接只會在通訊出現錯誤時,或是鏈接重建時關閉(一些防火牆常被設置爲丟棄過長的鏈接, 服務器端能夠設置一個超時時間, 超時後通知客戶端從新創建鏈接,並關閉原來的鏈接)。
使用 iframe 請求一個長鏈接有一個很明顯的不足之處:IE、Morzilla Firefox 下端的進度欄都會顯示加載沒有完成,並且 IE 上方的圖標會不停的轉動,表示加載正在進行。Google 的天才們使用一個稱爲「htmlfile」的 ActiveX 解決了在 IE 中的加載顯示問題,並將這種方法用到了 gmail+gtalk 產品中。Alex Russell 在 「What else is burried down in the depth's of Google's amazing JavaScript?」文章中介紹了這種方法。Zeitoun 網站提供的 comet-iframe.tar.gz,封裝了一個基於 iframe 和 htmlfile 的 JavaScript comet 對象,支持 IE、Mozilla Firefox 瀏覽器,能夠做爲參考