1. 程式人生 > >_web_ 端即時通訊技術盤點

_web_ 端即時通訊技術盤點

Ajax短輪詢:指令碼傳送的http請求

傳統的web應用要想與伺服器互動,必須提交一個表單(form),伺服器接收並處理傳來的表單,然後返回全新的頁面,因為前後兩個頁面的資料大部分都是相同的,這個過程傳輸了很多冗餘的資料、浪費了頻寬。於是Ajax技術便應運而生。
Ajax是Asynchronous JavaScript and XML的簡稱,由Jesse James Garrett 首先提出。這種技術開創性地允許瀏覽器指令碼(JS)傳送http請求。Outlook Web Access小組於98年使用,並很快成為IE4.0的一部分,但是這個技術一直很小眾,直到2005年初,google在他的goole groups、gmail等互動式應用中廣泛使用此種技術,才使得Ajax迅速被大家所接受。
Ajax的出現使客戶端與伺服器端傳輸資料少了很多,也快了很多,也滿足了以豐富使用者體驗為特點的web2.0時代 初期發展的需要,但是慢慢地也暴露了他的弊端。比如無法滿足即時通訊等富互動式應用的實時更新資料的要求。這種瀏覽器端的小技術畢竟還是基於http協議,http協議要求的請求/響應的模式也是無法改變的,除非http協議本身有所改變。

Comet:一種hack技術

以即時通訊為代表的web應用程式對資料的Low Latency要求,傳統的基於輪詢的方式已經無法滿足,而且也會帶來不好的使用者體驗。於是一種基於http長連線的“伺服器推”技術便被hack出來。這種技術被命名為Comet,這個術語由Dojo Toolkit 的專案主管Alex Russell在博文Comet: Low Latency Data for the Browser首次提出,並沿用下來。
其實,伺服器推很早就存在了,在經典的client/server模型中有廣泛使用,只是瀏覽器太懶了,並沒有對這種技術提供很好的支援。但是Ajax的出現使這種技術在瀏覽器上實現成為可能, google的gmail和gtalk的整合首先使用了這種技術。隨著一些關鍵問題的解決(比如 IE 的載入顯示問題),很快這種技術得到了認可,目前已經有很多成熟的開源Comet框架。
以下是典型的Ajax和Comet資料傳輸方式的對比,區別簡單明瞭。典型的Ajax通訊方式也是http協議的經典使用方式,要想取得資料,必須首先發送請求。在Low Latency要求比較高的web應用中,只能增加伺服器請求的頻率。Comet則不同,客戶端與伺服器端保持一個長連線,只有客戶端需要的資料更新時,伺服器才主動將資料推送給客戶端。
Comet的實現主要有兩種方式,基於Ajax的長輪詢(long-polling)方式和基於 Iframe 及 htmlfile 的流(http streaming)方式。

  1. 基於Ajax的長輪詢(long-polling)方式
    瀏覽器發出XMLHttpRequest 請求,伺服器端接收到請求後,會阻塞請求直到有資料或者超時才返回,瀏覽器JS在處理請求返回資訊(超時或有效資料)後再次發出請求,重新建立連線。在此期間伺服器端可能已經有新的資料到達,伺服器會選擇把資料儲存,直到重新建立連線,瀏覽器會把所有資料一次性取回。
  2. 基於 Iframe 及 htmlfile 的流(http streaming)方式
    Iframe是html標記,這個標記的src屬性會保持對指定伺服器的長連線請求,伺服器端則可以不停地返回資料,相對於第一種方式,這種方式跟傳統的伺服器推則更接近。
    在第一種方式中,瀏覽器在收到資料後會直接呼叫JS回撥函式,但是這種方式該如何響應資料呢?可以通過在返回資料中嵌入JS指令碼的方式,如 &lt;script type="text/javascript">js_func(“data from server ”)</script>
    ,伺服器端將返回的資料作為回撥函式的引數,瀏覽器在收到資料後就會執行這段JS指令碼。
    但是這種方式有一個明顯的不足之處:IE、Morzilla Firefox 下端的進度欄都會顯示載入沒有完成,而且 IE 上方的圖示會不停的轉動,表示載入正在進行。Google 的天才們使用一個稱為“htmlfile”的 ActiveX 解決了在 IE 中的載入顯示問題,並將這種方法應用到了 gmail+gtalk 產品中。

Websocket:未來的解決方案1

如果說Ajax的出現是網際網路發展的必然,那麼Comet技術的出現則更多透露出一種無奈,僅僅作為一種hack技術,因為沒有更好的解決方案。Comet解決的問題應該由誰來解決才是合理的呢?瀏覽器,html標準,還是http標準?主角應該是誰呢?本質上講,這涉及到資料傳輸方式,http協議應首當其衝,是時候改變一下這個懶惰的協議的請求/響應模式了。
W3C給出了答案,在新一代html標準html5中提供了一種瀏覽器和伺服器間進行全雙工通訊的網路技術Websocket。從Websocket草案得知,Websocket是一個全新的、獨立的協議,基於TCP協議,與http協議相容、卻不會融入http協議,僅僅作為html5的一部分。於是乎指令碼又被賦予了另一種能力:發起websocket請求。這種方式我們應該很熟悉,因為Ajax就是這麼做的,所不同的是,Ajax發起的是http請求而已。
與http協議不同的請求/響應模式不同,Websocket在建立連線之前有一個Handshake(Opening Handshake)過程,在關閉連線前也有一個Handshake(Closing Handshake)過程,建立連線之後,雙方即可雙向通訊。
有關WebSocket的詳細介,請參見即時通訊網有關WebSocket的系列文章:《WebSocket詳解(一):初步認識WebSocket技術》、《WebSocket詳解(二):技術原理、程式碼演示和應用案例》、《WebSocket詳解(三):深入WebSocket通訊協議細節》。
從瀏覽器支援角度來看,WebSocket已經近在眼前,但仍有一段較長的路要走,特別是在中國這個IE6、7、8依然盛行的國家,舊版本瀏覽器的消亡需要很長一段時間,在完全實現瀏覽器全相容前,Comet技術可能仍然是最好的解決方案。不過,當前也已存在一些比較成熟的封裝方案來解決這種相容性限制,比如:開源的Socket.io,詳見《Socket.IO介紹:支援WebSocket、用於WEB端的即時通訊的框架》。

SSE:未來的解決方案2

SSE(Server-Sent Event,服務端推送事件)是一種允許服務端向客戶端推送新資料的HTML5技術。與由客戶端每隔幾秒從服務端輪詢拉取新資料相比,這是一種更優的解決方案。
與WebSocket相比,它也能從服務端向客戶端推送資料。那如何決定你是用SSE還是WebSocket呢?概括來說,WebSocket能做的,SSE也能做,反之亦然,但在完成某些任務方面,它們各有千秋。
WebSocket是一種更為複雜的服務端實現技術,但它是真正的雙向傳輸技術,既能從服務端向客戶端推送資料,也能從客戶端向服務端推送資料。
WebSocket和SSE的瀏覽器支援率差不多,大多數主流桌面瀏覽器兩者都支援。在Android 4.3以及更早的版本中,系統預設瀏覽器兩者都不支援,Firefox和Chrome則完全支援;Android 4.4中,系統預設瀏覽器兩者都支援;Safari從5.0開始支援SSE(iOS系統從4.0開始),但直到6.0才正確地支援WebSocket(6.0之前的Safari所實現的WebSocket協議存在安全問題,所以一些主流瀏覽器已經禁用了基於這個協議的實現)。
與WebSocket相比,SSE有一些顯著的優勢。個人認為它最大的優勢就是便利:不需要新增任何新元件,用任何你習慣的後端語言和框架就能繼續使用。你不用為新建虛擬機器、弄一個新的IP或新的埠號而勞神,就像在現有網站中新增一個頁面那樣簡單。我喜歡把這稱為既存基礎設施優勢。
SSE的第二個優勢是服務端的簡潔。相對而言,WebSocket則很複雜,不借助輔助類庫基本搞不定(我試過,令人痛苦)。
因為SSE能在現有的HTTP/HTTPS協議上運作,所以它能直接運行於現有的代理伺服器和認證技術。而對WebSocket而言,代理伺服器需要做一些開發(或其他工作)才能支援,在寫這本書時,很多伺服器還沒有(雖然這種狀況會改善)。SSE還有一個優勢:它是一種文字協議,指令碼除錯非常容易。事實上,在本書中,我們會在開發和測試時用curl,甚至直接在命令列中執行後端指令碼。
不過,這就引出了WebSocket相較SSE的一個潛在優勢:WebSocket是二進位制協議,而SSE是文字協議(通常使用UTF-8編碼)。當然,我們可以通過SSE連線傳輸二進位制資料:在SSE中,只有兩個具有特殊意義的字元,它們是CR和LF,而對它們進行轉碼並不難。但用SSE傳輸二進位制資料時資料會變大,如果需要從服務端到客戶端傳輸大量的二進位制資料,最好還是用WebSocket。
WebSocket相較SSE最大的優勢在於它是雙向交流的,這意味向服務端傳送資料就像從服務端接收資料一樣簡單。用SSE時,一般通過一個獨立的Ajax請求從客戶端向服務端傳送資料。相對於WebSocket,這樣使用Ajax會增加開銷,但也就多一點點而已。如此一來,問題就變成了“什麼時候需要關心這個差異?”如果需要以1次/秒或者更快的頻率向服務端傳輸資料,那應該用WebSocket。0.2次/秒到1次/秒的頻率是一個灰色地帶,用WebSocket和用SSE差別不大;但如果你期望重負載,那就有必要確定基準點。頻率低於0.2次/秒左右時,兩者差別不大。
從服務端向客戶端傳輸資料的效能如何?如果是文字資料而非二進位制資料(如前文所提到的),SSE和WebSocket沒什麼區別。它們都用TCP/IP套接字,都是輕量級協議。延遲、頻寬、伺服器負載等都沒有區別,除非……呃?除非什麼?
當你在享用SSE的既存基礎設施優勢,並在客戶端和服務端指令碼之間設了一個網路伺服器,區別就顯現出來了。一個SSE連線不僅使用一個套接字,還會佔用一個Apache執行緒或程序,如果用PHP,它會為這個連線專門建立一個PHP新例項。Apache和PHP會使用大量的記憶體,這會限制伺服器所能支援的並行連線數。所以,要做到用SSE在資料傳輸效能上和WebSocket完全一樣,需要寫一個自己的後端伺服器,當然,那些在任何情況下都會用自己的伺服器並使用Node.js的人,會覺得這有什麼稀奇的。
說一下WebSocket在舊版本瀏覽器上的相容。當前,大約超過2/3的瀏覽器支援這些新技術,移動端瀏覽器的支援率會低一些。依慣例,每當需要雙向套接字時,就會用到Flash,並且WebSocket的向後相容通常是用Flash來做,這已經相當複雜了,如果瀏覽器上沒有Flash,情況更糟。概括來說,WebSocket難相容,SSE易相容。

WebSocket

websocket是html5規範中的一個部分,它借鑑了socket這種思想,為web應用程式客戶端和服務端之間(注意是客戶端服務端)提供了一種全雙工通訊機制。同時,它又是一種新的應用層協議,websocket協議是為了提供web應用程式和服務端全雙工通訊而專門制定的一種應用層協議,通常它表示為:ws://echo.websocket.org/?encoding=text HTTP/1.1,可以看到除了前面的協議名和http不同之外,它的表示地址就是傳統的url地址。
當客戶端向服務端傳送請求的時候,會發送如下報文:

這裡是報文。

可以看到,這是一個http get請求報文,注意該報文中有一個upgrade首部,它的作用是告訴服務端需要將通訊協議切換到websocket,如果服務端支援websocket協議,那麼它就會將自己的通訊協議切換到websocket,同時發給客戶端類似於以下的一個響應報文頭:

這裡是報文

返回的狀態碼為101,表示同意客戶端協議轉換請求,並將它轉換為websocket協議。以上過程都是利用http通訊完成的,稱之為websocket協議握手(websocket Protocol handshake),進過這握手之後,客戶端和服務端就建立了websocket連線,以後的通訊走的都是websocket協議了。所以總結為websocket握手需要藉助於http協議,建立連線後通訊過程使用websocket協議。同時需要了解的是,該websocket連線還是基於我們剛才發起http連線的那個TCP連線。一旦建立連線之後,我們就可以進行資料傳輸了,websocket提供兩種資料傳輸:文字資料和二進位制資料。
“Sec-WebSocket-Accept”的值是服務端採用與客戶端一致的金鑰計算出來後返回客戶端的,“HTTP/1.1 101 Switching Protocols”表示服務端接受 WebSocket 協議的客戶端連線,經過這樣的請求-響應處理後,客戶端服務端的 WebSocket 連線握手成功, 後續就可以進行 TCP 通訊了。讀者可以查閱WebSocket 協議棧瞭解 WebSocket 客戶端和服務端更詳細的互動資料格式。