服務器主動推送消息數據給客戶端

1 引言

這個問題第一次是我在實現一個導師的方案的時候所發現的,一開始我需要實現服務器與客戶端的密鑰協商和數據傳遞,服務器需要主動分發(推送)密鑰給客戶端,因爲以前沒有做過相關編碼,後來只能想到用反向連接,也就是交換C/S的身份

再後來是在做一個機器學習的問題時候,因爲機器學習模型的運行需要綜合多個客戶端(邊緣節點)的數據,然後得到結果,而且各個客戶端的數據傳輸是不一致的,時間和數據量不定。所以需要在服務器處理各個客戶端的數據,得到結果後主動推送結果給客戶端。所以準備研究一下這個問題:服務器主動推送消息數據給客戶端,包括web服務器與瀏覽器,後臺服務器與Android,服務器之間。

定義:推送技術是指通過客戶端與服務器端建立連接,客戶端可以接收由服務器端不定時主動發送的消息。

2 相關技術

這種技術分析來看在web端和Android端應該用的很多。比如服務器主動推送一條優惠信息給某個客戶端實時提醒客戶端有線上訂單消息,類似於郵件、消息、待辦事項等的通知

2.1 B/S架構

B/S架構的標準是基於HTTP協議,客戶端發送請求,服務器給出響應的一問一答形式進行通信。這就意味着如果客戶端不主動地向服務器發送消息,服務器就無法主動給客戶端推送消息。

隨着HTML、瀏覽器等各項技術、標準的發展,依次生成了不同的手段與方法能夠實現服務端主動推送消息,包括AJAX,Comet,ServerSent以及WebSocket。

適用場景:實時股票價格、商品價格、實時新聞、Twitter/weibo timeline、基於瀏覽器的聊天系統。

F5手動刷新 --> AJAX輪詢(Polling) --> Comet實時更新 --> HTML5實時通信

meta標籤

在 Web早期,通過配置meta標籤讓瀏覽器自動刷新,從而實現服務器端的推送

 <META HTTP-RQUIV="Refresh" CONTENT=12>

優點:使用方式簡單,可以在JS禁用情況下使用
缺點:不是實時更新數據,對服務器造成的壓力大,帶寬浪費多

2.1.1 AJAX輪詢

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

  1. 用戶向瀏覽器輸入需要訪問的地址URL
  2. 瀏覽器根據URL訪問服務器,並與服務器之間創建一個TCP連接(HTTP請求,也有可能是UDP)
  3. 服務器根據這個URL和一些參數,回覆一個響應,可能是一段HTML文本,也可能是一個圖片。將其寫入TCP連接,然後關閉連接(長連接則會保持多個請求-響應)
  4. 瀏覽器得到了來自服務器的HTML文本,解析並呈現渲染到瀏覽器上給用戶瀏覽

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

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

我們不難發現的是整個過程中間,一旦建立了一個連接,頁面就無法再維護住了。

Http輪詢:

即定時的通過Ajax查詢服務器端,客戶端定時向服務器端發送ajax請求,服務器端接收到請求後馬上響應信息並關閉連接。要求兩次請求間隔時間必須儘可能的小,但若時間間隔減小,客戶端瀏覽器在相同時間內就會發出更多的請求,這些請求中大部分都不會返回有用的數據,這會白白地浪費掉帶寬和處理資源。

JSONP輪詢:

JSONP輪詢和HTTP輪詢類似,不同之處在於使用JSONP可以發送跨域請求(請求不屬於您所在的域),JSONP請求通常可以通過它的調用參數和返回內容識別出來,其是可執行的JavaScript代碼。要想在 JavaScript 中實現輪詢,可以使用setInterval來定期地發出 Ajax 請求。

這種技術實現起來非常簡單,但它不具有伸縮性,需要不斷地向服務器端發送消息,會對服務器造成極大的性能浪費,加重網絡負載,拖累服務器。

Piggyback polling(捎帶輪詢):

捎帶輪詢是一種比輪詢更聰明的做法,它會刪除所有非必需的請求(沒有返回數據的那些),且不存在時間間隔,客戶端在需要的時候向服務器端發送請求。不同之處在於響應的部分,響應被分成兩個部分:對請求數據的響應和對服務器時間的響應。捎帶輪詢通常針對服務器端的所有 Ajax 請求可能會返回一個混合的響應。

這種方法因爲客戶端控制了何時發送請求,所以沒有不返回數據的請求,對資源的消耗較少,可用在所有瀏覽器上。但這仍然算是客戶端主動去請求服務器端,當服務器累積了事件想要傳送端戶端時,在客戶端沒有發送請求時也不能主動發送給客戶端。

 

此時我們學習一下XmlHttpRequest組件,這個組件提供我們手動創建一個HTTP請求,發送我們想要的數據,服務器也可以只返回我們想要的結果,最大的好處是,當我們收到服務器的響應時,原來的頁面沒有被摧毀。這就好比,我喊一句"我的咖啡喝完了,我要續杯",然後服務員就拿了一杯咖啡過來,而不是會把我沒喫完的套餐全部倒掉。術語就是局部刷新增量式的更新

其實就是定時的通過Ajax查詢服務端客戶端按規定時間定時像服務端發送ajax請求,服務器接到請求後馬上返回響應信息並關閉連接

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

其存在一個問題:

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

還有一個類似的輪詢是使用JSONP跨域請求的方式輪詢,在實現起來有差別,但基本原理都是相同的,都是客戶端不斷的向服務器發起請求。
優點:
服務端邏輯簡單,編碼實現簡單。
缺點:
這是通過模擬服務器發起的通信,不是實時通信,不顧及應用的狀態改變而盲目檢查更新,導致服務器資源的浪費,且會加重網絡負載,拖累服務器。其中大多數請求可能是無效請求,在大量用戶輪詢很頻繁的情況下對服務器的壓力很大。

應用:併發用戶量少,而且要求消息的實時性不高,一般很少採用。

會造成數據同步不及時無效的請求增加後端處理壓力

總結:嚴格地來說,這種實際方式,並不是真正意義上的服務器主動推送消息,但由於早期技術手段缺乏,所以AJAX輪循成爲了一種很普遍的手段。

2.1.2 Comet

輪詢方式的實時性是不夠的,比如基於Web的聊天功能,對實時性要求就很高。於是,comet出現了。Comet是基於HTTP長連接的服務器推送技術(long lived http),主要有流(streaming)方式長輪詢(long-polling)方式,都是基於AJAX。Comet工作原理:用戶發起請求後就掛起,等待服務器返回數據,在此期間不會斷開連接。流方式和長輪詢方式的區別是:對於流方式,客戶端發起連接就不會斷開連接,而是由服務器端進行控制。當服務器端有更新時,刷新數據,客戶端進行更新;而對於長輪詢,當服務器端有更新返回,客戶端先斷開連接,進行處理,然後重新發起連接

基於 HTTP 長連接的 "服務器推送" 技術,能使服務器端主動以異步的方式向客戶端程序推送數據,而不需要客戶端顯式的發出請求,目前有兩種實現方式:

1. 基於 AJAX 的長輪詢(long-polling)方式(長連接)
Ajax 的出現使得 JavaScript 可以調用 XMLHttpRequest 對象發出 HTTP 請求,JavaScript 響應處理函數根據服務器返回的信息對 HTML 頁面的顯示進行更新。使用 AJAX 實現 "服務器推送" 與傳統的 AJAX 應用不同之處在於:

  • 服務器端會阻塞請求直到有數據傳遞或超時才返回。(不是在一個長連接裏客戶端輪詢,而是基於請求-響應模型,在一個長連接裏等待服務器響應,稱之爲長輪詢
  • 客戶端 JavaScript 響應處理函數會在處理完服務器返回的信息後,再次發出請求,重新建立連接。
  • 當客戶端處理接收的數據、重新建立連接時,服務器端可能有新的數據到達。這些信息會被服務器端保存直到客戶端重新建立連接,客戶端會一次把當前服務器端所有的信息取回。

在Ajax輪詢基礎上做的一些改進,在沒有更新的時候不再返回空響應,而且把連接保持到有更新的時候,客戶端向服務器發送Ajax請求,服務器接到請求後hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息後再向服務器發送新的請求。

普通Ajax輪詢與基於Ajax的長輪詢原理對比: 

Ajax

基於長輪詢的服務器推模型

從上圖可以看出,客戶端發出請求後掛起,服務端在接到請求也掛起,等待有更新時返回數據並斷掉連接,然後客戶端再發起新的連接。
       相對於"輪詢"(poll),這種長輪詢方式也可以稱爲"拉"(pull)。因爲這種方案基於 AJAX,具有以下一些優點:請求異步發出;無須安裝插件;IE、Mozilla FireFox 都支持 AJAX。長輪詢 (long polling) 是在打開一條連接以後保持並等待服務器推送來數據再關閉,可以採用HTTP長輪詢XHR長輪詢兩種方式:
       (1). HTTP 和JSONP方式的長輪詢
       把 script 標籤附加到頁面上以讓腳本執行。服務器會掛起連接直到有事件發生,接着把腳本內容發送回瀏覽器,然後重新打開另一個 script 標籤來獲取下一個事件,從而實現長輪詢的模型

       實現可以使用 script 標籤或是單純的XMLHttpRequest對象來實現 HTTP 長輪詢。

       (2).XHR長輪詢
        這種方式是使用比較多的長輪詢模式。客戶端打開一個到服務器端的 AJAX 請求然後等待響應;服務器端需要一些特定的功能來允許請求被掛起,只要一有事件發生,服務器端就會在掛起的請求中送回響應並關閉該請求。客戶端 JavaScript 響應處理函數會在處理完服務器返回的信息後,再次發出請求,重新建立連接;如此循環。現在瀏覽器已經支持CROS的跨域方式請求,因此HTTP和JSONP的長輪詢方式是慢慢被淘汰的一種技術,建議採用XHR長輪詢
優點:

  • 客戶端很容易實現良好的錯誤處理系統和超時管理,實現成本與Ajax輪詢的方式類似。
  • 實時性高,無消息的情況下不會進行頻繁的請求。

缺點:

  • 需要服務器端有特殊的功能來臨時掛起連接。當客戶端發起的連接較多時,服務器端會長期保持多個連接,具有一定的風險。
  • 服務器維持着連接期間會消耗資源。

注>>在這裏簡單的說明下長輪詢,長連接的概念。

輪詢:客戶端定時向服務器發送Ajax請求,服務器接到請求後馬上返回響應信息並關閉連接。

優點:後端程序編寫比較容易。

缺點:請求中有大半是無用,浪費帶寬和服務器資源。

實例:適於小型應用。

長輪詢:客戶端向服務器發送Ajax請求,服務器接到請求後hold住連接,直到有新消息才返回響應信息並關閉連接,客戶端處理完響應信息後再向服務器發送新的請求。

long poll的原理是:客戶端與服務器將建立一條長連接,也就是說,客戶端會發出一個請求,而服務器,將阻塞請求,直到有數據需要傳遞,纔會返回。 返回之後,客戶端將關閉此連接,然後再次發出一個請求,建立一個新的連接,再次等待服務器推送數據。客戶端與服務器端的連接,會一直保存着,當發生改變時,服務器纔會把數據推送給客戶端。這其實,就是設計模式中的觀察者模式

優點:在無消息的情況下不會頻繁的請求。缺點:服務器hold連接會消耗資源。

實例:WebQQ、Hi網頁版、Facebook IM。

另外,對於長連接和socket連接也有區分:

長連接:在頁面裏嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設爲對一個長連接的請求,服務器端就能源源不斷地往客戶端輸入數據。一般指利用心跳包保持一個連接,從而延長連接持續時間。(持久連接一個HTTP連接,發送多個請求和響應)。

優點:消息即時到達,不發無用請求。缺點:服務器維護一個長連接會增加開銷。

實例:Gmail聊天 Flash

Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程序,JavaScript通過調用此Flash程序提供的Socket接口與服務器端的Socket接口進行通信,JavaScript在收到服務器端傳送的信息後控制頁面的顯示。

優點:實現真正的即時通信,而不是僞即時。缺點:客戶端必須安裝Flash插件;非HTTP協議,無法自動穿越防火牆。

實例:網絡互動遊戲。
2. 基於iframe 及 htmlfile 的流(streaming)方式
        iframe 是很早就存在的一種 HTML 標記, 通過在 HTML 頁面裏嵌入一個隱蔵frame,然後將這個隱蔵frame的 SRC 屬性設爲對一個長連接的請求,服務器端就能源源不斷地往客戶端輸入數據。iframe 流方式是在頁面中插入一個隱藏的 iframe,利用其 src 屬性在服務器和客戶端之間創建一條長鏈接,服務器向 iframe 傳輸數據(通常是 HTML,內有負責插入信息的 javascript),來實時更新頁面。從技術上來講,兩種常見的流技術包括 Forever Iframe(或者 hidden IFrame),或是被用來在 JavaScript 中創建 Ajax 請求的XMLHttpRequest對象的多部分 (multi-part) 特性
基於流方式的服務器推模型

從上圖可以看出,每次數據傳送不會關閉連接,連接只會在通信出現錯誤時,或是連接重建時關閉(一些防火牆常被設置爲丟棄過長的連接, 服務器端可以設置一個超時時間, 超時後通知客戶端重新建立連接,並關閉原來的連接)。

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 瀏覽器,可以作爲參考。


Comet的優缺點:
優點: 實時性好(消息延時小);性能好(能支持大量用戶)
缺點: 長期佔用連接,喪失了無狀態高併發的特點。
Comet實現框架:
1. Dojo CometD —— http://cometdproject.dojotoolkit.org/
2. DWR —— http://directwebremoting.org/dwr/index.html
3. ICEfaces —— http://www.icefaces.org/main/home/
4. GlassFish Grizzly —— https://grizzly.dev.java.net/
       CometD 目前實現 Comet 比較成熟, DWR 弱一些。 ICEfaces 更商業化,實現得很成熟。 Grizzly 是基於GlassFish ,也很成熟。CometD, DWR 開源性好。

CometD

CometD 框架是基於 HTTP 的事件驅動通信解決方案,使用了Bayeux通信協議,提供了一個 Java 服務器部件和一個 Java 客戶端部件,還有一個基於 jQuery 和 Dojo 的 JavaScript 客戶端庫。

Bayeux 通信協議主要是基於 HTTP,提供了客戶端與服務器之間的響應性雙向異步通信。Bayeux 協議基於通道進行通信,通過該通道從客戶端到服務器、從服務器到客戶端或從客戶端到客戶端(但是是通過服務器)路由和發送消息。Bayeux 是一種 “發佈- 訂閱” 協議。

CometD 與三個傳輸協議綁定在一起:JSON、JSONP 和 WebSocket。他們都依賴於 Jetty Continuations 和 Jetty WebSocket API。在默認情況下,可以在 Jetty 6、Jetty 7、和 Jetty 8 中以及其他所有支持 Servlet 3.0 Specification 的服務中使用 CometD。

服務器和內部構件

Atmosphere框架

Atmosphere提供了一個通用 API,以便使用許多 Web 服務器(包括 Tomcat、Jetty、GlassFish、Weblogic、Grizzly、JBossWeb、JBoss 和 Resin)的 Comet 和 WebSocket 特性。它支持任何支持 Servlet 3.0 Specification 的 Web 服務器。

Atmosphere 提供了一個 jQuery 客戶端庫,該庫可以使連接設置變得更容易,它能夠自動檢測可以使用的最佳傳輸協議(WebSockets 或 CometD)。Atmosphere 的 jQuery 插件的用法與 HTML5 WebSockets API 相似。

Pushlet

Pushlet 使用了觀察者模型:客戶端發送請求,訂閱感興趣的事件;服務器端爲每個客戶端分配一個會話 ID 作爲標記,事件源會把新產生的事件以多播的方式發送到訂閱者的事件隊列裏。

Pushlet 最後更新於2010年2月5號,之後至今沒有再更新。

Cometd 和Atmosphere框架參見示例代碼 (https://github.com/brucefengnju/cometdatoms)。

Bayeux 是一套基於 Publish / Subscribe 模式,以 JSON 格式在瀏覽器與服務器之間傳輸事件的通信協議。該協議規定了瀏覽器與服務器之問的雙向通信機制,克服了傳統 Web 通信模式的缺點。

Bayeux 協議主要基於 HTTP 來傳輸低延遲的、異步的事件消息。這些消息通過頻道 (Channels) 來投遞,能夠實現從服務器端到客戶端、從客戶端到服務器端或者通過服務器從一個客戶端到另一個客戶端的傳送。Bayeux 協議的主要目的是爲使用了 Ajax 和 Comet 技術的 Web 客戶端實現高響應的用戶交互。Bayeux 協議旨在通過允許執行者更容易的實現互操作性,來降低開發 Comet 應用程序的複雜性。它解決了共同的消息發佈和路由問題,並提供了漸進式的改進和擴展機制。

Dojo 已經對 Cometd 做了封裝,基於 Dojo 的 Cometd 包,我們不用再浪費大量的代碼在搭建 Cometd 框架上。對於前端腳本代碼,我們只需要加上一個 Cometd 包的簡單接口代碼,便可以開始加入我們自己的業務邏輯代碼了。

 

Comet實現要點:
不要在同一客戶端同時使用超過兩個的 HTTP 長連接
我們使用 IE 下載文件時會有這樣的體驗,從同一個 Web 服務器下載文件,最多隻能有兩個文件同時被下載。第三個文件的下載會被阻塞,直到前面下載的文件下載完畢。這是因爲 HTTP 1.1 規範中規定,客戶端不應該與服務器端建立超過兩個的 HTTP 連接, 新的連接會被阻塞。而 IE 在實現中嚴格遵守了這種規定。
HTTP 1.1 對兩個長連接的限制,會對使用了長連接的 Web 應用帶來如下現象:在客戶端如果打開超過兩個的 IE 窗口去訪問同一個使用了長連接的 Web 服務器,第三個 IE 窗口的 HTTP 請求被前兩個窗口的長連接阻塞。所以在開發長連接的應用時, 必須注意在使用了多個 frame 的頁面中,不要爲每個 frame 的頁面都建立一個 HTTP 長連接,這樣會阻塞其它的 HTTP 請求,在設計上考慮讓多個 frame 的src共用一個長連接。
服務器端的性能和可擴展性
一般 Web 服務器會爲每個連接創建一個線程,如果在大型的商業應用中使用 Comet,服務器端需要維護大量併發的長連接。在這種應用背景下,服務器端需要考慮負載均衡和集羣技術;或是在服務器端爲長連接作一些改進。
       應用和技術的發展總是帶來新的需求,從而推動新技術的發展。HTTP 1.1 與 1.0 規範有一個很大的不同:1.0 規範下服務器在處理完每個 Get/Post 請求後會關閉套接口連接; 而 1.1 規範下服務器會保持這個連接,在處理兩個請求的間隔時間裏,這個連接處於空閒狀態。 Java 1.4 引入了支持異步 IO 的 java.nio 包。當連接處於空閒時,爲這個連接分配的線程資源會返還到線程池,可以供新的連接使用當原來處於空閒的連接的客戶發出新的請求,會從線程池裏分配一個線程資源處理這個請求。 這種技術在連接處於空閒的機率較高、併發連接數目很多的場景下對於降低服務器的資源負載非常有效。
       但是 AJAX 的應用使請求的出現變得頻繁,而 Comet 則會長時間佔用一個連接,上述的服務器模型在新的應用背景下會變得非常低效,線程池裏有限的線程數甚至可能會阻塞新的連接。Jetty 6 Web 服務器針對 AJAX、Comet 應用的特點進行了很多創新的改進。
控制信息與數據信息使用不同的 HTTP 連接
       使用長連接時,存在一個很常見的場景:客戶端網頁需要關閉,而服務器端還處在讀取數據的堵塞狀態,客戶端需要及時通知服務器端關閉數據連接。服務器在收到關閉請求後首先要從讀取數據的阻塞狀態喚醒,然後釋放爲這個客戶端分配的資源,再關閉連接。所以在設計上,我們需要使客戶端的控制請求和數據請求使用不同的 HTTP 連接,才能使控制請求不會被阻塞。
       在實現上,如果是基於 iframe 流方式的長連接,客戶端頁面需要使用兩個 iframe,一個是控制幀,用於往服務器端發送控制請求,控制請求能很快收到響應,不會被堵塞;一個是顯示幀,用於往服務器端發送長連接請求。如果是基於 AJAX 的長輪詢方式,客戶端可以異步地發出一個 XMLHttpRequest 請求,通知服務器端關閉數據連接。
在客戶和服務器之間保持“心跳”信息
       在瀏覽器與服務器之間維持一個長連接會爲通信帶來一些不確定性:因爲數據傳輸是隨機的,客戶端不知道何時服務器纔有數據傳送。服務器端需要確保當客戶端不再工作時,釋放爲這個客戶端分配的資源,防止內存泄漏。因此需要一種機制使雙方知道大家都在正常運行。在實現上:服務器端在阻塞讀時會設置一個時限,超時後阻塞讀調用會返回,同時發給客戶端沒有新數據到達的心跳信息。此時如果客戶端已經關閉,服務器往通道寫數據會出現異常,服務器端就會及時釋放爲這個客戶端分配的資源。如果客戶端使用的是基於 AJAX 的長輪詢方式,服務器端返回數據、關閉連接後,經過某個時限沒有收到客戶端的再次請求,會認爲客戶端不能正常工作,會釋放爲這個客戶端分配、維護的資源。當服務器處理信息出現異常情況,需要發送錯誤信息通知客戶端,同時釋放資源、關閉連接。

我們知道HTTP請求其實是基於TCP連接實現的(也有少部分基於UDP),再看看之前說的HTTP請求處理過程:

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

看到這個處理過程,我們不難聯想到,如果把第4步——關閉連接給省掉,那不就相當於有一個長連接一直被維持住了麼。通過對服務端的一些操作,我們可以直接將數據從這個TCP連接發送客戶端了。

通過這種技術,我們可以大大提高服務器推送的實時性,還可以減去服務端不停地建立、施放連接所形成的開銷。

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

  1. 建立連接後依然使用"詢問"+"應答"的模式。雖然工作方式沒變,但是因爲使用長連接,減去了每次建立與施放連接的工作,所以性能上提升了很多。而且服務器對TCP連接可以有上下文的定義,而不像以前的AJAX完全是無狀態的
  2. 通過對Stream的寫入實現服務器將數據主動發送到客戶端。因爲是TCP連接,所以通過對服務器的編程,我們可以主動的把數據從服務端發送給客戶端,從模式上真正建立起了推送的概念

總結:基本實現了的服務器主動推送消息。服務器可以隨時可以推送數據給客戶端,但需要保持一個長連接,浪費資源。

2.1.3 Server-Sent

Server-Sent是HTML5提出一個標準,它延用了Comet的思路,並對其進行了一些規範。使得Comet這項技術由原來的分支衍生技術轉成了正統的官方標準。Server-Sent Events包含新的HTML元素EventSource新的MIME類型 text/event-stream,這個MIME類型定義了事件框架格式。Server-Sent Event是基於HTTP streaming的。response會一直打開,當服務器端有事件發生的時候,事件會被寫入response中。

Server-sent-events(SSE)讓服務端可以向客戶端流式發送文本消息,在實現上,客戶端瀏覽器中增加EventSource對象,使其能通過事件的方式接收到服務器推送的消息,在服務端,使用長連接的事件流協議,即請求響應時增加新數據流數據格式。非常適應於後端數據更新頻繁且對實時性要求較高而又不需要客戶端向服務端通信的場景下。

它的原理與Comet相同,由客戶端發起與服務器之間創建TCP連接,然後並維持這個連接,至到客戶端或服務器中的做任何一放斷開,ServerSent使用的是"問"+"答"的機制,連接創建後瀏覽器會週期性地發送消息至服務器詢問,是否有自己的消息。本質還是輪詢,只不過維持一個長連接,在長連接裏進行輪詢。

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

優點

  • 基於現有http協議,實現簡單
  • 斷開後自動重連,並可設置重連超時
  • 派發任意事件
  • 跨域並有相應的安全過濾

缺點

  • 只能單向通信,服務器端向客戶端推送事件
  • 事件流協議只能傳輸UTF-8數據,不支持二進制流。
  • IE下目前不支持EventSource
  • 如果代理服務器或中間設備不支持SSE,會導致連接失效,正式環境中使用通過TLS協議發送SSE事件流。

總結:總得來說SeverSent就是HTML5規範下的Comet,具有更好的統一性,而且簡單好用。嚴格來說,技術本身沒有什麼改進。

過渡:

插件提供 socket 方式:比如利用 Flash XMLSocket,Java Applet 套接口,Activex 包裝的 socket。

  • 優點:原生 socket 的支持,和 PC 端和移動端的實現方式相似。
  • 缺點:瀏覽器端需要裝相應的插件。

2.1.4 WebSocket

Comet和SSE仍然是單向通信(服務器向客戶端),不能適應Web應用的飛速發展。於是,各種新技術不斷湧現,其中WebSocket在Google的力推之下已經成爲業界標準,並不斷完善中。其基於TCP之上定義了幀協議,支持雙向的通信。

WebSocket是一種全新的協議,不屬於http無狀態協議,協議名爲ws協議或wss協議。ws不是http,所以傳統的web服務器不一定支持。一個websocket連接地址會是這樣的寫法:ws://wilsonliu.cn:8080/webSocketServer。

WebSocket是HTML5開始提供的一種在單個 TCP 連接上進行全雙工通訊的協議。WebSocket通訊協議於2011年被IETF定爲標準RFC 6455,WebSocket API被W3C定爲標準。在WebSocket API中,瀏覽器和服務器只需要做一個握手的動作,然後,瀏覽器和服務器之間就形成了一條快速通道。兩者之間就直接可以數據互相傳送。實現了瀏覽器與服務器的全雙工通信,擴展了瀏覽器與服務端的通信功能,使服務端也能主動向客戶端發送數據。

看名字就知道了,這是一個可以用在瀏覽器上的Socket連接。這也是一個HTML5標準中的一項內容,他要求瀏覽器可以通過JavaScript腳本手動創建一個TCP連接與服務端進行通訊。因爲Socket是一個全雙工的TCP和UDP連接,所以不需要輪詢,只需要保持長連接和心跳包。通信協議的header也很小,相比與之前的long poll, web socket 能夠更好的節省服務器資源和寬帶並達到實時通信。

WebSocket通信協議包含兩個部分,一是開放性HTTP握手連接協商連接參數二是二進制消息分幀機制(接收消息的文本和二進制數據傳輸)。它是一個獨立完善的協議,也可以在瀏覽器之外實現。

另外WebSocket使用了ws和wss協議(表示使用加密信道通信(TCP + TLS),支持接收和發送文本和二進制數據,需要服務器有與之握手的算法才能將連接打開。

所以WebSocket相對於之前幾種手段來說,其編碼量是最大的(Socket的編碼量就是大),但由於沒有其它的約束,因此它也可以自由地實現所有可能的功能。

即可以滿足"問"+"答"的響應機制,也可以實現主動推送的功能。與ServerSent相同,HTML5也對WebSocket調用的JavaScript進行規範。WebSocket具有較爲複雜的協議,需要在服務端做額外編程才能進行數據通訊。

總結:將Socket的建立移植到了瀏覽器的腳本端,JS可以創建連接與服務器通訊,包括請求-響應模型主動輪詢的推送模式等。技術本身沒有什麼改進。最大特點就是,服務器可以主動向客戶端推送信息,客戶端也可以主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。

服務端實現

  • Node (Socket.IO, WebSocket-Node, ws)
  • Java (Jetty)
  • Python (Tornado, pywebsocket)
  • swoole:官方寫的websocket php客戶端 https://github.com/swoole/swoole-src/edit/master/examples/websocket/WebSocketClient.php
  • ...

使用場景:

適合於對數據的實時性要求比較強的場景,如通信、股票、Feed、直播、共享桌面,特別適合於客戶端與服務頻繁交互的情況下,如實時共享、多人協作等平臺

優點:

  • 真正的全雙工通信
  • 支持跨域設置(Access-Control-Allow-Origin)
  • 更好的節省服務器資源和帶寬並達到實時通訊

缺點

  • 採用新的協議,後端需要單獨實現
  • 客戶端並不是所有瀏覽器都支持,目前還未普及,瀏覽器支持不好
  • 代理服務器會有不支持websocket的情況
  • 無超時處理
  • 更耗電及佔用資源

代理和很多現有的HTTP中間設備可能不理解新的WebSocket協議,而這可能導致各種問題,使用時需要注意,可以使藉助TLS,通過建立一條端到端的加密信道,可以讓WebSocket通信繞過所有中間代理。

HTML5 WebSocket 設計出來的目的就是要取代輪詢和 Comet 技術,使客戶端瀏覽器具備像 C/S 架構下桌面系統的實時通訊能力。瀏覽器向服務器發出建立 WebSocket 連接的請求,連接建立以後,客戶端和服務器端就可以通過 TCP 連接直接交換數據。因爲 WebSocket 連接本質上就是一個 TCP 連接,所以在數據傳輸的穩定性和數據傳輸量的大小方面,和輪詢以及 Comet 技術比較,具有很大的性能優勢。

使用方法:

WebSocket 協議本質上是一個基於 TCP 協議。爲了建立一個 WebSocket 連接,客戶端瀏覽器首先要向服務器發起一個 HTTP 請求,這個請求和通常的 HTTP 請求不同,包含了一些附加頭信息,其中附加頭信息 ”Upgrade: WebSocket” 表明這是一個申請協議升級的 HTTP 請求(詳細的 WebSocket 消息的內容這裏就不詳細說了,基本和 HTTP 的差不多,而且都是由 WebSocket 對象自動發送和接收的,對用戶透明),服務器端解析這些附加的頭信息然後產生應答信息返回給客戶端,客戶端和服務器端的 WebSocket 連接就建立起來了,雙方就可以通過這個連接通道自由的傳遞信息,並且這個連接會持續存在直到客戶端或者服務器端的某一方主動的關閉連接。

WebSocket API 最偉大之處在於服務器和客戶端可以在給定的時間範圍內的任意時刻,相互推送信息。WebSocket 並不限於以 Ajax (或 XmlHttpRequest)方式通信,因爲 Ajax 技術需要客戶端發起請求,而 WebSocket 服務器和客戶端可以彼此相互推送信息;XmlHttpRequest 通信受到域的限制,而 WebSocket 允許跨域通信

需要注意的問題是,除了安全和性能以外,服務端只管往 socket 裏面寫數據就可以了,WebSocket 的通信數據全部是以 ”\x00″ 開頭以 ”\xFF” 結尾的,無論是服務端發出的數據還是客戶端發送的數據都遵從這個格式,唯一不同的是客戶端的 WebSocket 對象能夠自動將頭尾去除,獲得主體數據,這就省卻了在客戶端處理原始數據的必要,而且 WebSocket 通信的消息總是 UTF-8 格式的。

應用場景:

WebSocket廣泛應用於社交聊天、直播、彈幕、多玩家遊戲、協同編輯、股票基金實時報價、體育實況更新、視頻會議/聊天、基於位置的應用、在線教育、智能家居等需要高實時的場景

WebSocket特點:

  • 建立在 TCP 協議之上,服務器端的實現比較容易。
  • 與 HTTP 協議有着良好的兼容性,能通過各種 HTTP 代理服務器。
  • 數據格式比較輕量,性能開銷小,通信高效。
  • 可以發送文本,也可以發送二進制數據。
  • 沒有同源限制,客戶端可以與任意服務器通信。
  • 協議標識符是ws(如果加密,則爲wss),服務器網址就是 URL。
  • 能實現真正意義上的數據推送。

2.1.5 WebSocket + MessageQueue

MessageQueue,簡稱MQ,也就是消息隊列。是一種常常用於TCP服務端的技術。通過生產和訪問各種消息類型,MQ服務器會將生產者所生成的消息發給感興趣的客戶端。市面上有很多的MQ框架,比如:ActiveMQ。本質上是一種發佈-訂閱模式

ActiveMQ已經支持了WebSocket協議,也就意味着,WebSocket已經可以作爲一個生產者或一個消費者,與MQ服務器連接。

開發者可以通過MQTT的JS腳本,連接上MQ服務器,同時將Web服務器也連上MQ服務器,從此可以告別了HTTP通訊協議,完完全全使用Socket通訊來完成數據的交換。

Java消息服務(Java Message Service)是message數據傳輸的典型的實現方式,是一種發佈-訂閱模式。系統A和系統B通過一個消息服務器進行數據交換。系統A發送消息到消息服務器,如果系統B訂閱系統A發送過來的消息,消息服務器會消息推送給B。雙方約定消息格式即可。目前市場上有很多開源的jms消息中間件,比如  ActiveMQ, OpenJMS 。

2.2 C/S架構

C/S架構主要指服務器與應用之間的通信架構,比如服務器與Android App之間的通信,不包括基於H5的移動App。

其實上述的B/S架構所用的六種方法可以移植或者借鑑到C/S架構。所以下述的幾種方法都是C/S架構特有的方法。

2.2.1 反向連接

       如果不是B/S架構,可以交換C/S身份,進行連接。我們稱之爲反向/反彈連接,有點類似於反彈木馬的行爲方式。此時(原服務器端)客戶端向服務器端(原客戶端端)發送通信數據包。

此時要求服務器有公網IP,並且FW不限制連接建立。

2.2.2 SMS(Push)方式

      SMS(Push)方式:通過攔截SMS消息並且解析消息內容來了解服務器的命令,但這種方式一般用戶在經濟上很難承受。

2.2.3 現有的推送方案

A、C2DM雲端推送方案

       在Android手機平臺上,Google提供了C2DM(Cloud to Device Messaging)服務。Android Cloud to Device Messaging (C2DM)是一個用來幫助開發者從服務器向Android應用程序發送數據的服務。該服務提供了一個簡單的、輕量級的機制,允許服務器可以通知移動應用程序直接與服務器進行通信,以便於從服務器獲取應用程序更新和用戶數據。

總結:服務器發送通知給C2DM服務器,C2DM服務器發送通知給Android系統的C2DM服務,C2DM服務通知移動應用主動連接服務器,以獲取服務器發送的數據。

該方案存在的主要問題是C2DM需要依賴於Google官方提供的C2DM服務器,由於國內的網絡環境,這個服務經常不可用。

B、MQTT協議實現Android推送

       採用MQTT協議實現Android推送功能也是一種解決方案(https://legacy.gitbook.com/book/mcxiaoke/mqtt-cn/details)。MQTT是一個輕量級的消息發佈/訂閱協議,它是實現基於手機客戶端的消息推送服務器的理想解決方案。 wmqtt.jar 是IBM提供的MQTT協議的實現。我們可以從這裏(https://github.com/tokudu/AndroidPushNotificationsDemo)下載該項目的實例代碼,並且可以找到一個採用PHP書寫的服務器端實現(https://github.com/tokudu/PhpMQTTClient)。

C、RSMB實現推送功能

       Really Small Message Broker (RSMB) ,是一個簡單的MQTT代理,同樣由IBM提供,其查看地址是:http://www.alphaworks.ibm.com/tech/rsmb。缺省打開1883端口,應用程序當中,它負責接收來自服務器的消息並將其轉發給指定的移動設備。SAM是一個針對MQTT寫的PHP庫。我們可以從這個http://pecl.php.net/package/sam/download/0.2.0地址下載它。

D、XMPP協議實現Android推送

       Google官方的C2DM服務器底層也是採用XMPP協議進行的封裝XMPP(可擴展通訊和表示協議)是基於可擴展標記語言(XML)的協議,它用於即時消息(IM)以及在線探測。這個協議可能最終允許因特網用戶向因特網上的其他任何人發送即時消息。 
       androidpn是一個基於XMPP協議的java開源Android push notification實現。它包含了完整的客戶端和服務器端。但也存在一些不足之處: 
1) 比如時間過長時,就再也收不到推送的信息了。 
2)性能上也不夠穩定。 
3)將消息從服務器上推送出去,服務器就不再管理了,不管消息是否成功到達客戶端手機上。 
如果我們要使用androidpn,則還需要做大量的工作,需要理解XMPP協議、理解Androidpn的實現機制,需要調試內部存在的BUG。

E、使用第三方平臺

       目前國內、國外有一些推送平臺可供使用,但是涉及到收費問題、保密問題、服務質量問題、擴展問題等等。百度雲,個推等。

實際上,主流的移動平臺都已經有系統級的推送產品,Android上有GCM(Google Cloud Messaging),iOS上有APNS(Apple Push Notification service),WinPhone有MPNS(Microsoft Push Notification service)。但GCM在國內處於不可用狀態(一、國內大部分Android手機都不帶Google服務,也就用不了GCM,這是主要的問題。 二、在國內Google的服務一般都不太穩定),所以國內的移動應用採用另外一種做法---在後臺運行一個Service,維持應用於服務端的TCP長連接,以達到實時消息送達的效果。

但是在移動端如何穩定的維持長連接是一件非常複雜的事情,前面說了,客戶端通過定時發送心跳信號(Heartbeat)以維持與服務端的長連接,但是,如果心跳的頻率太頻繁,移動設備耗電增加,心跳間隔太久又可能使得連接被斷開。並且普遍認爲移動設備處於一個多變的網絡環境中,WIFI,2G,4G切換,基站切換都會引起網絡變動,在不同網絡環境下的心跳頻率,與網絡變動的重連動作,都需要大量的數據統計分析總結出來。

這僅僅是客戶端的難題,在如今移動應用動輒成百上千的用戶量的情況下,如何維護如此多的長連接,如果應對大規模的消息下發以及後續針對下發消息的各種統計動作都是技術難點。

再者,現在應用一般都是全平臺的,發送一條消息,應該同時發送給Android,iOS, WinPhone,Android端走自建的TCP長連接通道,iOS與WinPhone走自家的系統推送通道。那麼意味着你服務端要維護這三套推送系統。

顯然對於小團隊,要獨自建立一套消息推送系統的難度非常大,所以市場上湧現出很多優秀的推送產品,幫開發者聚合這些推送方式,並提供統一的推送接口。國外如 Urban Airship, Parse, 國內有JPush百度雲推送信鴿LeanCloud等。(比較遺憾的是,非常優秀的Parse已經被Facebook宣佈停止開發,並將於1年後關閉)

現在除了體量非常大的公司自建推送系統外,一般普通公司都是使用第三方推送服務,以上所有的第三方推送服務,基礎功能都是免費的。

移動推送技術

IOS

首先是IOS平臺,IOS的推送是通過蘋果自己的APNs服務進行的,用戶需要將device_token(設備號+包名)以及消息內容等推送信息交給APNs服務器,剩下的均由蘋果自己來完成。但是如果提供的device_token是失效的(app被卸載、系統版本升級導致device_token變化等情況)那麼推送過程就會被中斷,頻繁的斷線重連甚至會被APNs認爲是一直DoS攻擊。

對於iOS App來說,一般情況下(有例外情況),App不允許後臺長期運行。所以當App被系統掛起或殺掉後,推送只能通過APNS送達。APNS通知到達手機後,由用戶喚起App,App運行起來後,就能夠啓動自己的長連接(如XMPP),完成進一步的推送。當然,APNS的推送速度肯定比不上自己的長連接,所以一般iOS上還一般採用僞推送的方式。App在退到後臺後能短暫執行幾分鐘,長連接也會持續一段時間。如果在這段時間內推送可以通過自己的長連接到達,那麼App在客戶端產生一個本地推送(類似於系統產生的遠程推送)。

圖中,Provider是指某個iPhone軟件的Push服務器,這篇文章我將使用.net作爲Provider。

APNS 是Apple Push Notification Service(Apple Push服務器)的縮寫,是蘋果的服務器。

上圖可以分爲三個階段。

第一階段:.net應用程序把要發送的消息、目的iPhone的標識打包,發給APNS。

第二階段:APNS在自身的已註冊Push服務的iPhone列表中,查找有相應標識的iPhone,並把消息發到iPhone。

第三階段:iPhone把發來的消息傳遞給相應的應用程序, 並且按照設定彈出Push通知。

iOS 系統的推送(APNS,即 Apple Push Notification Service)依託一個或幾個系統常駐進程運作,是全局的(接管所有應用的消息推送),所以可看作是獨立於應用之外,而且是設備和蘋果服務器之間的通訊,而非應用的提供商服務器。你的例子裏面,騰訊 QQ 的服務器(Provider)會給蘋果公司對應的服務器(APNs)發出通知,然後再中轉傳送到你的設備(Devices)之上。當你接收到通知,打開應用,纔開始從騰訊服務器接收數據,跟你之前看到通知裏內容一樣,但卻是經由兩個不同的通道而來。

Android

而在Android上,跟APNS對應的服務GCM,在國內無法使用。而各個手機廠商(如小米)自己的推送服務又無法覆蓋所有手機終端。所以,在Android上實現推送只能自己啓動Service維持一個長連接。正是因爲安卓上很多應用都有後臺長連接掛着,所以安卓手機更費流量,也更費電。Android平臺在不使用GCM的情況下就需要將自己的服務器或是第三方推送服務提供商的服務器與設備建立一條長連接,通過長連接進行推送。但是不建議自己設置服務器實現推送功能,一是因爲成本太高(開發成本、維護成本),自己搭建的服務器無論是穩定性還是速度上都比不了第三方推送服務提供商的效果。另一個是因爲自己的數據量較小,使用第三方推送服務提供商可以用他們的維度進行推送,實現精準推送。友盟推送就是做的比較好的,可以根據用戶分羣、地區、語言等多維度進行推送,最大程度減少對於用戶的干擾,僅把消息推送給相關用戶。

開發者通過第三方推送服務提供商將信息直接下發給需要的設備,第三方推送服務提供商與設備建立一條長連接通道,並且將消息路由到APP中(圖中的設備1與設備2),對於像設備3這種無網絡連接或是沒有成功建立長連接通道的設備,會在設備3連網且推送消息沒有過期的情況下自動收到由第三方推送服務提供商推送過來的消息,保證消息不會丟失。

 

3 詳細介紹

分析來看,以上介紹的服務器主動推送技術主要分爲:

3.1 短連接輪詢

前端用定時器,每間隔一段時間發送請求來獲取數據是否更新,這種方式可兼容ie和支持高級瀏覽器。通常採取setInterval或者setTimeout實現

通過遞歸的方法,在獲取到數據後每隔一定時間再次發送請求,這樣雖然無法保證兩次請求間隔爲指定時間(數據的生成時間),但是獲取的數據順序得到保證。 

缺點:

1、頁面會出現‘假死’

setTimeout在等到每次EventLoop時,都要判斷是否到指定時間,直到時間到再執行函數,一旦遇到頁面有大量任務或者返回時間特別耗時,頁面就會出現‘假死’,無法響應用戶行爲。

2、無謂的網絡傳輸

當客戶端按固定頻率向服務器發起請求,數據可能並沒有更新,浪費服務器資源。特別是每次輪詢時候的握手,揮手建立連接。

3.2  長輪詢

客戶端像傳統輪詢一樣從服務端請求數據,服務端會阻塞請求不會立刻返回,直到有數據或超時才返回給客戶端,然後關閉連接,客戶端處理完響應信息後再向服務器發送新的請求。

長輪詢解決了頻繁的網絡請求,建立多次連接,浪費服務器資源,並且可以及時返回給瀏覽器。

缺點:

1、保持連接也會消耗資源,佔用帶寬。

2、若果服務器沒有返回有效數據,程序超時。

3.3  iframe流

iframe流方式是在頁面中插入一個隱藏的iframe,利用其src屬性在服務器和客戶端之間創建一條長連接,服務器向iframe傳輸數據(通常是HTML,內有負責插入信息的javascript),來實時更新頁面

  • 前端實現步驟:

1、Iframe設置爲不顯示。

2、src設爲請求的數據地址。

3、定義個父級函數,讓用戶iframe子頁面調用傳數據給父頁面。

4、定義onload事件,服務器timeout後再次重新加載iframe。

  • 後端輸出內容:

當有新消息時服務端會向iframe中輸入一段js代碼.:println("<script>父級函數('" + 數據 +"<br>')</script>”);用於調用父級函數傳數據。

  • 優點:

iframe流方式的優點是瀏覽器兼容好,Google公司在一些產品中使用了iframe流,如Google Talk

  • 缺點:

1、IE、Mozilla Firefox會顯示加載沒有完成,圖標會不停旋轉。

2、服務器維護一個長連接會增加開銷。

3.4  WebSocket

WebSocket是一種全新的協議,隨着HTML5草案的不斷完善,越來越多的現代瀏覽器開始全面支持WebSocket技術了,它將TCP的Socket(套接字)應用在了webpage上,從而使通信雙方建立起一個保持在活動狀態連接通道

      運行流程:瀏覽器通過JavaScript向服務端發出建立WebSocket連接的請求,在WebSocket連接建立成功後,客戶端和服務端就可以通過 TCP連接傳輸數據。因爲WebSocket連接本質上是TCP連接,不需要每次傳輸都帶上重複的頭部數據,所以它的數據傳輸量比輪詢和Comet技術小 了很多。

JavaEE 7中出了JSR-356:Java API for WebSocket規範。不少Web容器,如Tomcat,Nginx,Jetty等都支持WebSocket。Tomcat從7.0.27開始支持 WebSocket,從7.0.47開始支持JSR-356。

  • 原理:

WebSocket協議是借用HTTP協議的101 switch protocol(服務器根據客戶端的指定,將協議轉換成爲 Upgrade首部所列的協議)來達到協議轉換的,從HTTP協議切換成WebSocket通信協議。

  • 具體連接方式:

通過在請求頭中增加 upgrade:websocket 及通信密鑰(Sec-WebSocket-Key),使雙方握手成功,建立全雙工通信。

WebSocket客戶端連接報文: 

WebSocket服務端響應報文:

  • 通信過程:

websocket是純事件驅動的,一旦 WebSocket 連接建立後,通過監聽事件可以處理到來的數據和改變的連接狀態。數據都以幀序列的形式傳輸。服務端發送數據後,消息和事件會異步到達。WebSocket編程遵循一個異步編程模型,只需要對WebSocket對象增加回調函數就可以監聽事件。

(websocket示意圖)

 3.5 Server-sent Events(SSE)

sse與長輪詢機制類似,區別是每個連接不只發送一個消息。客戶端發送一個請求,服務端保持這個連接直到有新消息發送回客戶端,仍然保持着連接,這樣連接就可以再次發送消息的,由服務器單向發送給客戶端

原理:

SSE本質是發送的不是一次性的數據包,而是一個數據流。可以使用 HTTP 301 和 307 重定向與正常的 HTTP 請求一樣。服務端連續不斷的發送,客戶端不會關閉連接,如果連接斷開,瀏覽器會嘗試重新連接。如果連接被關閉,客戶端可以被告知使用 HTTP 204 無內容響應代碼停止重新連接。

SSE只適用於高級瀏覽器,ie不支持。因爲ie上的XMLHttpRequest對象不支持獲取部分的響應內容,只有在響應完成之後才能獲取其內容。

 

短輪詢

長輪詢

Websocket

sse

通訊方式

http

http

基於TCP長連接通訊

http

觸發方式

輪詢

輪詢

事件

事件

優點

兼容性好容錯性強,實現簡單

 

全雙工通訊協議,性能開銷小、安全性高,有一定可擴展性

實現簡便,開發成本低

缺點

安全性差,佔較多的內存資源與請求數

安全性差,佔較多的內存資源與請求數

傳輸數據需要進行二次解析,增加開發成本及難度

只適用高級瀏覽器

適用範圍

b/s服務

b/s服務

網絡遊戲、銀行交互和支付

服務端到客戶端單向推送

TCP是全雙工通訊,也就是隻要知道雙方socket,雙方都可以發送數據。 

 

sse

websocket

輪詢

服務器部署

×

×

瀏覽器兼容性

×

×

後端推送

×

 

4 總結

4.1 技術總結

B/S架構

1、AJAX的(短)輪詢。(PULL拉取)

2、Comet和SSE利用長輪詢和長連接,基本實現了服務器主動推送。但其本質上依舊是基於請求-響應模式,但對實現和服務器端要求不高,技術變化不大。(PUSH推送)

以上倆種方法都不是真正的主動推送技術。

3、WebSocket通過建立全雙工通信的Socket,實現了真正的服務器推送功能。對服務器和編碼要求較高。(PUSH推)

4、發佈-訂閱模式,需要專門的服務器。

C/S架構

1、如果不是B/S架構,可以交換C/S身份,進行連接。我們稱之爲反向/反彈連接。此時客戶端發送輪詢數據包,進行週期性詢問和請求。類似於反彈木馬的行爲方式。

2、SMS(Push)方式:通過攔截SMS消息並且解析消息內容來了解服務器的命令,但這種方式一般用戶在經濟上很難承受。

4.2 經驗總結

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

對比這兩種方式:

ServerSent的方式,可以使服務端的開發依然依用以前的方式,但是其工作方式與Comet類似。特別是對於老項目的翻新,還是用SeverSent比較好。

而WebSocket的方式,則對服務端的開發有着較高的要求,但其工作方式是完全的推送。真正實現了Web的實時通信,使B/S模式具備了C/S模式的實時通信能力。

(2)

對於簡單的推送需求又不考慮兼容低版本瀏覽器,推薦使用server-sent Events

如果需要多條雙向數據實時交互或需要二進制傳輸,推薦websocket。

對於還要考慮低版本瀏覽器,那麼還是用輪詢(基本AJAX)來實現功能。

(3)

IM、實時對戰遊戲之類的應用場景,就一定需要長連接推送。像天氣類,新聞類 app,對推送實時性要求並不太高,採取定期拉取策略也是可以的(實際上實現難度要降低幾個數量級)。

(4)

考慮問題:

(1)採用什麼協議?XMPP還是MQTT還是自定義二進制協議?是否像微信一樣,需要推送二進制數據(比如短語音和縮略圖數據)?
(2)如何保證後臺長連接不死?像大公司的各個App,普通會採用互拉的手段保持後臺運行。而最牛的還是微信,在各個手機系統上基本都算是“開掛”了。
(3)如何做才能真正不丟數據?涉及到系統的方方面面,比如消息的確認,客戶端和服務器的數據同步,客戶端的數據存儲的事務保證,後臺消息隊列如何設計保證不丟數據。如果是IM,離線數據如何處理?
(4)長連接的Keep Alive和連接狀態的檢測。比如XMPP相當於一個永遠解析不完的XML流,使用一個空格作爲Keep Alive消息。
(5)在iOS上進行僞推送。前面已經提到過。還有如何利用iOS特性,用特殊APNS推送喚起App預先下載消息。
(6)長連接的安全性。驗證以及加密。
(7)是用各種雲推送還是自己實現?用哪家雲推送更好?

實現方法:

1. html5 websocket

2. WebSocket 通過 Flash

3. XHR長時間連接

4. XHR Multipart Streaming

5. 不可見的Iframe

6. <script>標籤的長時間連接(可跨域)

(4)基礎實現;提供的推送系統API;第三方推送服務平臺。

(5)nodejs的http://socket.io,它是websocket的一個開源實現,對不支持websocket的瀏覽器降級成comet / ajax 輪詢,http://socket.io的良好封裝使代碼編寫非常容易。

4.3 實例

同步會議和即時通信是推送服務的典型事例。聊天信息和臨時文件一旦被髮送,用戶就會通過推送服務接收到。分離的 peer-to-peer 程序(例如 WASTE )和集中的程序(例如 IRC 或 XMPP )都允許推送文件,這就意味着發件人開始進行數據的傳送,而不是收件人。

Email 可能也是一個推送系統:SMTP 協議是一個推送協議(見 Push e-mail)。然而,從郵件服務器到桌面計算機的最後一步通常使用的是像 POP3 或 IMAP 這樣的 pull 協議現代電子郵件客戶端通過反覆輪詢郵件服務器,使這一步看起來是瞬間的,它經常檢查是否有新郵件。IMAP 協議包括 IDLE command,它允許服務器當新郵件到達時向客戶端發消息。初代的黑莓是第一個在無線環境下推送電子郵件的設備,使得它成爲當今的佳話。

其他例子是 PointCast Network (點播式網絡),它在二十世紀九十年代具有非常廣泛的應用。它通過屏保推送新聞和股票行情。在瀏覽器戰爭的巔峯時期,Netscape和 Microsoft 通過在他們的軟件裏集成頻道定義格式(CDF)推送技術,但是那時並沒有多少人關注。 CDF 消失了並移出了瀏覽器的時代,取而代之的是2000年的 RSS(一種下拉式系統)。

4.3.1 網頁推送

這個網絡工程師極力推崇的網頁推送方案是一個簡單的協議,通過使用 HTTPv2.0 版本的去即時的事件。例如來電或留言,能夠被即時的傳達(或者說推送)出去。這個協議將所有即時的事件都合併到一個簡單的會話中,其中這個會話可以確保不管在網絡還是音頻資源上都有很好的使用效率。這一個簡單的服務包含了所有的事件,並在當它到達分發給對應的應用所有的事件。這個請求只需要一個會話,避免了重複發送這樣高昂的成本。

4.3.2 HTTP服務器推送

HTTP 服務器推送(也稱爲 HTTP 流)是一種將未經請求的(異步)數據從Web服務器發送到Web瀏覽器的機制。任何一種機制都可以實現 HTTP 服務的推送。

一些 HTML5 的 WebSocket API 允許 Web 服務器和客戶端,通過 TCP 全雙工通信進行交流。

一般情況下,當響應完畢一個來自客戶端的請求之後,Web 服務器不會去終止一個連接。 Web 服務器使連接打開,以便當發生事件(例如,需要向一個或多個客戶端報告的內部數據的變化)時,可以立即發送它;否則,該事件必須排隊,直到接收到客戶端的下一個請求爲止。大多數 Web 服務器都通過 CGI 提供這種功能(例如,Apache HTTP 服務器上未解析的報頭腳本)。這是一種靠分塊傳輸編碼方法的基本機制。

另一種機制關係到一個特殊的 MIME 類型,稱爲 multipart/x-mixed-replace,這是由 Netscape 在1995年引入的。當服務器想向客戶端推出新版本時,Web 瀏覽器將此理解爲一個文件改變。它如今仍然被 Firefox 、 Opera 和 Safari 支持,但它被 Internet Explorer 忽略了。它可以應用於 HTML 文檔,以及用於流式傳輸圖像的相機應用。

網頁超文本應用技術工作小組(WHATWG)Web Applications 1.0 proposal 包括一個機制來推送內容到客戶端。2006年9月1日, Opera Web 瀏覽器在一個名爲“服務器發送事件”的功能中實現了這個新的實驗系統。現在它作爲 HTML5 的一部分被標準化了。

4.3.3 推送技術

在這個技術,服務器充分利用持久的 HTTP 協議,使得響應永久打開,(即服務器永不關閉響應),有效地去欺騙瀏覽器保持加載,直到初始頁面被認爲完全加載完畢之後。然後服務器週期的發送 JavaScript 代碼去更新這網頁的內容。從而實現推送能力,通過使用這個技術,客戶不需要JAVA程序或者其他的插件程序去保持與服務器的連接。客戶端也會自動地收到新事件,推送給服務器。這個方法有一個嚴重的不足,就是缺乏一種控制,服務器已經結束進程使瀏覽器連接超時;一個頁面如果發生連接超時必然會導致其刷新。

4.3.4 長輪詢

長輪詢本身並不是一個真正的推送;長輪詢是傳統輪詢技術的一種變體,但是它允許在一個真正的推送不可能的情況下模擬推送機制,例如安全策略的網站需要阻止的HTTP/S請求。

長輪詢, 客戶端從服務器請求信息與正常輪詢完全相同,但正如你預料到的,服務器可能不會立即迴應。當收到輪詢時如果服務器的客戶端沒有新的信息,不是發送一個空的響應,服務器端公開請求並等待成爲有用的響應信息。一旦它獲得新的信息,服務器會立即向客戶端發送 HTTP/S 的響應,完成開放 HTTP 的請求。收到服務器響應後,客戶端通常會立即發出另一個服務器的請求,這樣,通常的反應延遲(信息第一次可用到下一個客戶端請求的中間時間)與客戶端的消除相關。

例如,當這樣的連接是困難的或不可能直接採用的(例如,在web瀏覽器),作爲對連續 TCP 連接的長輪詢的替代,BOSH 是一個流行的、長壽的 HTTP 技術。在 XMPP是一個潛在的技術,蘋果用於 iCloud 的推送支持。

4.3.5 Flash XMLSocket 傳達

這項技術被 cbox(Cbox is a chat application for online communities and groups. Get a Cbox, and your visitors and users can engage with one another in real-time conversation.) 和其他聊天應用程序所使用,並在一個單像素 Adobe Flash 電影中使用 XMLSocket 對象。在 JavaScript 控制下,客戶端創建一個 TCP 連接到服務器上的單向中繼。中繼服務器不會從這個套接字讀取任何內容,相反它會向客戶端發送一個唯一標識符。接下來客戶機向 web 服務器發送 HTTP 請求,包括這個標識符。Web 應用程序可以將向客戶機發送的消息發送到中繼服務器的本地接口,然後中繼服務器通過 Flash 套接字傳遞給客戶機。這種方法的優點是,它能鑑別許多 web 應用程序的典型的讀寫不對稱,包括聊天,它還提供了更高的效率。由於它不接受傳出套接字的數據,所以中繼服務器根本不需要輪着傳出 TCP 連接,使其保持數萬個併發連接成爲可能。在這個模型中擴展的限制是底層服務器操作系統的 TCP 堆棧。

4.3.6 可靠的組數據傳送 (RGDD)

例如雲計算的服務,增加數據的可靠性和可用性,這通常被推送(被複制)到多臺機器上。例如, Hadoop 分佈式文件系統(數據庫)對所有的儲存對象複製兩個副本。RGDD 重點研究有效的鑄造一個對象從一個位置到多個位置與此同時通過發送極小的數目的副本來節省寬帶(在最好的情況下只有一個)在任何跨越網絡的對象。例如,數據廣播是一個在數據中心傳遞多個節點,依靠規則和結構化拓撲和 DCCast 是一個類似接近跨過數據中心交付方法的方案

4.3.7 電子郵件

傳統的移動郵件客戶端可以通過頻繁向郵件服務器查詢新郵件來模擬郵件推送的用戶體驗。IMAP協議實際上允許在任意時間發送多個(不包括郵件數據的)通知。IDLE(閒置)就是這樣一種功能,它告知服務器可以在任何時候發送通知,並通過客戶端運行發送通知以外的命令,從而有效地提供了一個等同於推送的用戶體驗。

IMAP POP3 都沒有推送,只能定時查詢。foxmail 這種也是定時去刷新登錄的。

除非是一些專有協議:Exchange ActiveSync,Mapi 。

 

總結:

  優點 缺點
推/PUSH 實時性高,管理方便,相對節省流量 隨時維護一個長連接,浪費電池電量,浪費系統資源
拉/PULL 靈活性高,實現簡單,相對節省電量 不斷地訪問服務器,可能帶來網絡帶寬的浪費

“推”主要用到的是推送機制,當後臺服務器有消息更新時立即發送給應用程序;“拉”主要用到的是輪詢機制,應用程序不定時的頻繁訪問後臺服務器以獲取最新的消息。

輪詢機制是客戶端每隔一段時間向服務器發送 請求以確定是否有數據更新,如果有,返回更新數據;否則,返回空。輪詢方式
分爲兩類,普通輪詢和長輪詢。普通輪詢指的是當服務器響應客戶端的請求後立即關閉連接,這樣可以保證開發的簡易性和操作的便捷性,但會帶來巨大的資源和帶寬浪費。長輪詢指的是當服務器響應客戶端的請求後並不立即關閉連接,而是保持一段時間,當客戶端回覆終止包或者連接超時才關閉,這樣可以避免無數據更新時的頻繁請求,但是會帶來服務器資源的浪費。減少了輪詢的次數
 
推送機制是服務器開啓服務後建立持久連接,當服務器有數據更新時直接將其發送到客戶端,避免了多次建立連接。
目前,實現 Android 平臺信息推送的解決方案主要有四種,基於 GCM 服務基於 XMPP 協議基於 MQTT 協議,使用第三方平臺(如百度推送,極光推送)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章