這裡介紹 HTML5 的 WebSocket 概念,並且跟傳統的即時性網頁技術 Polling、Long-Polling 與 Streaming 做比較。
HTML5 的 WebSocket 是一種建立在單一 TCP 連線上的全雙工(full-duplex)通訊管道,可以讓網頁應用程式與伺服器之間做即時性、雙向的資料傳遞。
WebSocket 跟以往實作全雙工的技術比起來,改進了非常多,不但減低網路頻寬的使用,也降低了網路延遲的時間。(關於網路的頻寬與延遲可參考這裡)
當使用者在瀏覽網頁時,瀏覽器會傳送一個 HTTP 請求到網頁伺服器上,然後伺服器會根據這個請求將網頁的內容傳回給瀏覽器,但是在很多的情況下,使用者會需要看到最新的即時性資訊,例如觀看股票市場行情、監控網路流量等等,而在以前使用者只能靠著重新載入網頁才能獲得最新的資訊,但是這樣不但很浪費時間,也會佔用很多不必要的網路資源,並不是一個好的方式。
針對這個問題,目前已經有發展出許多技術可以解決,例如輪詢(polling)或 Comet 這類的伺服器端 push 技術,透過延遲 HTTP 回應的方式達到及時傳遞訊息的方法,而 Comet 這類的實作通常都是使用 JavaScript 配合長時間輪詢(long-polling)或串流(streaming)來達成。
輪詢(polling)的做法是讓瀏覽器每隔一段時間就自動送出一個 HTTP 請求給伺服器,獲取最新的網頁資料,這個做法是最老舊的方式,如果你已經事先知道伺服器上資料更新的頻率或時間,那麼就可以使用這樣的方式讓瀏覽器上的資料同步更新,但是在許多即時性的網頁應用程式上,情況並不是這樣,你通常不會知道伺服器上的資料何時會更新,在伺服器沒有新資料時,瀏覽器如果也送出 HTTP 請求,就會造成浪費網路資源的狀況。
長時間輪詢(long-polling)則是讓伺服器在接收到瀏覽器所送出 HTTP 請求後,伺服器會等待一段時間,若在這段時間裡面伺服器有新的資料,它就會把最新的資料傳回給瀏覽器,如果等待的時間到了之後也沒有新資料的話,就會送一個回應給瀏覽器,告知瀏覽器資料沒有更新。
雖然長時間輪詢可以減少產生原本輪詢(polling)造成網路頻寬浪費的狀況,但是如果在資料更新很頻繁的狀況下,長時間輪詢並不會比傳統的輪詢有效率,而且有時候資料量很大時,會造成連續的 polls 不斷產生,反而會更糟糕。
串流(streaming)是讓伺服器在接收到瀏覽器所送出 HTTP 請求後,立即產生一個回應瀏覽器的連線,並且讓這個連線持續一段時間不要中斷,而伺服器在這段時間內如果有新的資料,就可以透過這個連線將資料馬上傳送給瀏覽器。
這個方式雖然不錯,但是由於他是建立在 HTTP 協定上的一種傳輸機制,所以有可能會因為代理伺服器(proxy)或防火牆(firewall)將其中的資料存放在緩衝區中,造成資料回應上的延遲,因此許多使用串流的 Comet 實作會在偵測到有代理伺服器的狀況時,改用長時間輪詢的方式處理。另外透過 TLS(SSL)的連線也可以避免緩衝區的問題,但是這個方式除了設定麻煩之外,也會造成伺服器額外的負擔。
以上這些即時性更新網頁的技術都有使用到 HTTP 的請求與回應,所以在網路上傳輸的資料中,一定會包含 HTTP 的表頭資訊,而這些資料其實不是必要的,多傳輸這些資料反而會造成網路延遲上升。
如果是全雙工的連線的話,只需要一條就可以同時處理上傳與下載的資料傳輸,而如果以半雙工的 HTTP 協定要達到全雙工的效果,大部份的實作方式都會開啟兩條連線,一條負責上傳、另一條負責下載,但是這樣的做法不但讓整個系統更加複雜、難以維護,而且伺服器的負擔也會增加。
下圖中這個架構是一個使用半雙工 HTTP 協定的 Comet 應用程式架構,而後端搭配一個 messaging broker 以 publish/subscribe 的方式提供及時的資料。
複雜的 Comet 網頁應用程式架構 |
這個狀況在你要擴充系統的規模時會更糟糕,使用 HTTP 來實作雙向的資料傳輸是一件很麻煩的事情,在維護很容易出問題,擴充也會有困難,縱使你的使用者感覺這樣即時性的網頁應用程式很好用,但是使用這樣的架構同時會讓你的伺服器與網路承受很大的工作負載量。
WebSocket 是定義在 HTML5 標準中的一個新的網頁傳輸方式,可在一條連線上提供全雙工、雙向的資料傳輸,在這樣的標準下你可以很容易實作一個兼具可擴充性與即時性的網頁應用程式。另外因為 WebSocket 提供瀏覽器一個原生(native)的 socket,所以直接解決了 Comet 架構很容易出錯的問題,而在整個架構的複雜度上也會比傳統的做法簡單很多。
瀏覽器與伺服器之間若要建立一條 WebSocket 連線,在一開始的交握(handshake)階段中,要先從 HTTP 協定升級為 WebSocket 協定,瀏覽器送出:
GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13
而伺服器回應:
HTTP/1.1 101 Switching Protocols Upgrade: websocket Connection: Upgrade Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= Sec-WebSocket-Protocol: chat
當連線用的 socket 建立之後,WebSocket 的 data frames 就可以在瀏覽器與伺服器之間以全雙工的模式進行雙向的傳輸,不管是文字或是二進位的資料都沒問題。
WebSocket 的在將資料打包成 data frame 時,在最好的狀況下只會多出 2 個位元組(bytes)。如果是文字的資料,每一個 frame 會以一個 0x00
這個位元組開頭,最後以 0xFF
這個位元組結束,中間的部分就是 UTF-8 的文字資料,若是二進位的資料,就會以 length prefix 來判斷。不管如何其資料量都會比傳統的 HTTP 協定小很多。
這裡只是簡略說明 WebSocket 的通訊協定概念,更詳細規範可以參考 RFC6455 的說明。
在 WebSocket 的官方網站上實作了一個股票監控網頁,比較了傳統 Polling 與新的 WebSocket 的效能差異,以下是一些實際的測試數據報告。
首先是網路頻寬(bandwidth)的比較,WebSocket 的架構所使用的網路頻寬比傳統的 Polling 小非常多。
另外這個是網路延遲(latency)的比較,由於 WebSocket 在通訊協定上的改進,所以在網路延遲也會比傳統 Polling 所使用的 HTTP 小很多。
若要查看詳細的實驗設計與測試過程,可以參考 WebSocket 的官方網站。