1. 程式人生 > >WebSocket 原理介紹及伺服器搭建

WebSocket 原理介紹及伺服器搭建

WebSocket是html5新增加的一種通訊協議,目前流行的瀏覽器都支援這個協議,例如Chrome,Safari,Firefox,Opera,IE等等,對該協議支援最早的應該是chrome,從chrome12就已經開始支援,隨著協議草案的不斷變化,各個瀏覽器對協議的實現也在不停的更新。該協議還是草案,沒有成為標準,不過成為標準應該只是時間問題了,從WebSocket草案的提出到現在已經有十幾個版本了,目前最新的是版本17,所對應的協議版本號為13,目前對該協議支援最完善的瀏覽器應該是chrome,畢竟WebSocket協議草案也是Google釋出的。

1.     WebSocket API簡介

首先看一段簡單的javascript程式碼,該程式碼呼叫了WebSockets的API。

[javascript] view plaincopy在CODE上檢視程式碼片派生到我的程式碼片
  1. var ws = new WebSocket(“ws://echo.websocket.org”);
  2. ws.onopen = function(){ws.send(“Test!”); };  
  3. ws.onmessage = function(evt){console.log(evt.data);ws.close();};  
  4. ws.onclose = function(evt){console.log(“WebSocketClosed!”);};  
  5. ws.onerror = function(evt){console.log(“WebSocketError!”);};  

這份程式碼總共只有5行,現在簡單概述一下這5行程式碼的意義。

第一行程式碼是在申請一個WebSocket物件,引數是需要連線的伺服器端的地址,同http協議使用http://開頭一樣,WebSocket協議的URL使用ws://開頭,另外安全的WebSocket協議使用wss://開頭。

第二行到第五行為WebSocket物件註冊訊息的處理函式,WebSocket物件一共支援四個訊息 onopen, onmessage, onclose和onerror,當Browser和WebSocketServer連線成功後,會觸發onopen訊息;如果連線失敗,傳送、接收資料失敗或者處理資料出現錯誤,browser會觸發onerror訊息;當Browser接收到WebSocketServer傳送過來的資料時,就會觸發onmessage訊息,引數evt中包含server傳輸過來的資料;當Browser接收到WebSocketServer端傳送的關閉連線請求時,就會觸發onclose訊息。我們可以看出所有的操作都是採用訊息的方式觸發的,這樣就不會阻塞UI,使得UI有更快的響應時間,得到更好的使用者體驗。

Browser已經支援http協議,為什麼還要開發一種新的WebSocket協議呢?我們知道http協議是一種單向的網路協議,在建立連線後,它只允許Browser/UA(UserAgent)向WebServer發出請求資源後,WebServer才能返回相應的資料。而WebServer不能主動的推送資料給Browser/UA,當初這麼設計http協議也是有原因的,假設WebServer能主動的推送資料給Browser/UA,那Browser/UA就太容易受到攻擊,一些廣告商也會主動的把一些廣告資訊在不經意間強行的傳輸給客戶端,這不能不說是一個災難。那麼單向的http協議給現在的網站或Web應用程式開發帶來了哪些問題呢?

讓我們來看一個案例,現在假設我們想開發一個基於Web的應用程式去獲取當前Web伺服器的實時資料,例如股票的實時行情,火車票的剩餘票數等等,這就需要Browser/UA與WebServer端之間反覆的進行http通訊,Browser不斷的傳送Get請求,去獲取當前的實時資料。下面介紹幾種常見的方式:

1.     Polling


這種方式就是通過Browser/UA定時的向Web伺服器傳送http的Get請求,伺服器收到請求後,就把最新的資料發回給客戶端(Browser/UA),Browser/UA得到資料後,就將其顯示出來,然後再定期的重複這一過程。雖然這樣可以滿足需求,但是也仍然存在一些問題,例如在某段時間內Web伺服器端沒有更新的資料,但是Browser/UA仍然需要定時的傳送Get請求過來詢問,那麼Web伺服器就把以前的老資料再傳送過來,Browser/UA把這些沒有變化的資料再顯示出來,這樣顯然既浪費了網路頻寬,又浪費了CPU的利用率。如果說把Browser傳送Get請求的週期調大一些,就可以緩解這一問題,但是如果在Web伺服器端的資料更新很快時,這樣又不能保證Web應用程式獲取資料的實時性。

2.     Long Polling


上面介紹了Polling遇到的問題,現在介紹一下LongPolling,它是對Polling的一種改進。

Browser/UA傳送Get請求到Web伺服器,這時Web伺服器可以做兩件事情,第一,如果伺服器端有新的資料需要傳送,就立即把資料發回給Browser/UA,Browser/UA收到資料後,立即再發送Get請求給Web Server;第二,如果伺服器端沒有新的資料需要傳送,這裡與Polling方法不同的是,伺服器不是立即傳送迴應給Browser/UA,而是把這個請求保持住,等待有新的資料到來時,再來響應這個請求;當然了,如果伺服器的資料長期沒有更新,一段時間後,這個Get請求就會超時,Browser/UA收到超時訊息後,再立即傳送一個新的Get請求給伺服器。然後依次迴圈這個過程。

這種方式雖然在某種程度上減小了網路頻寬和CPU利用率等問題,但是仍然存在缺陷,例如假設伺服器端的資料更新速率較快,伺服器在傳送一個數據包給Browser後必須等待Browser的下一個Get請求到來,才能傳遞第二個更新的資料包給Browser,那麼這樣的話,Browser顯示實時資料最快的時間為2×RTT(往返時間),另外在網路擁塞的情況下,這個應該是不能讓使用者接受的。另外,由於http資料包的頭部資料量往往很大(通常有400多個位元組),但是真正被伺服器需要的資料卻很少(有時只有10個位元組左右),這樣的資料包在網路上週期性的傳輸,難免對網路頻寬是一種浪費。

通過上面的分析可知,要是在Browser能有一種新的網路協議,能支援客戶端和伺服器端的雙向通訊,而且協議的頭部又不那麼龐大就好了。WebSocket就是肩負這樣一個使命登上舞臺的。


WebSocket協議是一種雙向通訊協議,它建立在TCP之上,同http一樣通過TCP來傳輸資料,但是它和http最大的不同有兩點:1.WebSocket是一種雙向通訊協議,在建立連線後,WebSocket伺服器和Browser/UA都能主動的向對方傳送或接收資料,就像Socket一樣,不同的是WebSocket是一種建立在Web基礎上的一種簡單模擬Socket的協議;2.WebSocket需要通過握手連線,類似於TCP它也需要客戶端和伺服器端進行握手連線,連線成功後才能相互通訊。

下面是一個簡單的建立握手的時序圖:

這裡簡單說明一下WebSocket握手的過程。

當Web應用程式呼叫new WebSocket(url)介面時,Browser就開始了與地址為url的WebServer建立握手連線的過程。

1.     Browser與WebSocket伺服器通過TCP三次握手建立連線,如果這個建立連線失敗,那麼後面的過程就不會執行,Web應用程式將收到錯誤訊息通知。

2.     在TCP建立連線成功後,Browser/UA通過http協議傳送WebSocket支援的版本號,協議的字版本號,原始地址,主機地址等等一些列欄位給伺服器端。

例如:

  1. GET /chat HTTP/1.1  
  2. Host: server.example.com  
  3. Upgrade: websocket  
  4. Connection: Upgrade  
  5. Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==  
  6. Origin: http://example.com  
  7. Sec-WebSocket-Protocol: chat,superchat  
  8. Sec-WebSocket-Version: 13  

3.     WebSocket伺服器收到Browser/UA傳送來的握手請求後,如果資料包資料和格式正確,客戶端和伺服器端的協議版本號匹配等等,就接受本次握手連線,並給出相應的資料回覆,同樣回覆的資料包也是採用http協議傳輸。

  1. HTTP/1.1 101 Switching Protocols  
  2. Upgrade: websocket  
  3. Connection: Upgrade  
  4. Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  
  5. Sec-WebSocket-Protocol: chat  

4.     Browser收到伺服器回覆的資料包後,如果資料包內容、格式都沒有問題的話,就表示本次連線成功,觸發onopen訊息,此時Web開發者就可以在此時通過send介面想伺服器傳送資料。否則,握手連線失敗,Web應用程式會收到onerror訊息,並且能知道連線失敗的原因。

WebSocket與http協議一樣都是基於TCP的,所以他們都是可靠的協議,Web開發者呼叫的WebSocket的send函式在browser的實現中最終都是通過TCP的系統介面進行傳輸的。WebSocket和Http協議一樣都屬於應用層的協議,那麼他們之間有沒有什麼關係呢?答案是肯定的,WebSocket在建立握手連線時,資料是通過http協議傳輸的,正如我們上一節所看到的“GET/chat HTTP/1.1”,這裡面用到的只是http協議一些簡單的欄位。但是在建立連線之後,真正的資料傳輸階段是不需要http協議參與的。

具體關係可以參考下圖:

WebSocket(5)-- WebSocket Server

如果要搭建一個Web伺服器,我們會有很多選擇,市場上也有很多成熟的產品供我們應用,比如開源的Apache,安裝後只需簡單的配置(或者預設配置)就可以工作了。但是如果想搭建一個WebSocket伺服器就沒有那麼輕鬆了,因為WebSocket是一種新的通訊協議,目前還是草案,沒有成為標準,市場上也沒有成熟的WebSocket伺服器或者Library實現WebSocket協議,我們就必須自己動手寫程式碼去解析和組裝WebSocket的資料包。要這樣完成一個WebSocket伺服器,估計所有的人都想放棄,幸好的是市場上有幾款比較好的開源庫供我們使用,比如PyWebSocket,WebSocket-Node, LibWebSockets等等,這些庫檔案已經實現了WebSocket資料包的封裝和解析,我們可以呼叫這些介面,這在很大程度上減少了我們的工作量。

下面就簡單介紹一下這些開源的庫檔案。

1.     PyWebSocket

PyWebSocket採用Python語言編寫,可以很好的跨平臺,擴充套件起來也比較簡單,目前WebKit採用它搭建WebSocket伺服器來做LayoutTest。

我們可以獲取原始碼通過下面的命令

svn checkouthttp://pywebsocket.googlecode.com/svn/trunk/ pywebsocket-read-only

2.     WebSocket-Node

WebSocket-Node採用JavaScript語言編寫,這個庫是建立在nodejs之上的,對於熟悉JavaScript的朋友可參考一下,另外Html5和Web應用程式受歡迎的程度越來越高,nodejs也正受到廣泛的關注。

我們可以從下面的連線中獲取原始碼

3.     LibWebSockets

LibWebSockets採用C/C++語言編寫,可定製化的力度更大,從TCP監聽開始到封包的完成我們都可以參與程式設計。

我們可以從下面的命令獲取原始碼

git clone git://git.warmcat.com/libwebsockets


WebSocket是html5新增加的一種通訊協議,目前流行的瀏覽器都支援這個協議,例如Chrome,Safari,Firefox,Opera,IE等等,對該協議支援最早的應該是chrome,從chrome12就已經開始支援,隨著協議草案的不斷變化,各個瀏覽器對協議的實現也在不停的更新。該協議還是草案,沒有成為標準,不過成為標準應該只是時間問題了,從WebSocket草案的提出到現在已經有十幾個版本了,目前最新的是版本17,所對應的協議版本號為13,目前對該協議支援最完善的瀏覽器應該是chrome,畢竟WebSocket協議草案也是Google釋出的。

1.     WebSocket API簡介

首先看一段簡單的javascript程式碼,該程式碼呼叫了WebSockets的API。

[javascript] view plaincopy在CODE上檢視程式碼片派生到我的程式碼片
  1. var ws = new WebSocket(“ws://echo.websocket.org”);
  2. ws.onopen = function(){ws.send(“Test!”); };  
  3. ws.onmessage = function(evt){console.log(evt.data);ws.close();};  
  4. ws.onclose = function(evt){console.log(“WebSocketClosed!”);};  
  5. ws.onerror = function(evt){console.log(“WebSocketError!”);};  

這份程式碼總共只有5行,現在簡單概述一下這5行程式碼的意義。

第一行程式碼是在申請一個WebSocket物件,引數是需要連線的伺服器端的地址,同http協議使用http://開頭一樣,WebSocket協議的URL使用ws://開頭,另外安全的WebSocket協議使用wss://開頭。

第二行到第五行為WebSocket物件註冊訊息的處理函式,WebSocket物件一共支援四個訊息 onopen, onmessage, onclose和onerror,當Browser和WebSocketServer連線成功後,會觸發onopen訊息;如果連線失敗,傳送、接收資料失敗或者處理資料出現錯誤,browser會觸發onerror訊息;當Browser接收到WebSocketServer傳送過來的資料時,就會觸發onmessage訊息,引數evt中包含server傳輸過來的資料;當Browser接收到WebSocketServer端傳送的關閉連線請求時,就會觸發onclose訊息。我們可以看出所有的操作都是採用訊息的方式觸發的,這樣就不會阻塞UI,使得UI有更快的響應時間,得到更好的使用者體驗。

Browser已經支援http協議,為什麼還要開發一種新的WebSocket協議呢?我們知道http協議是一種單向的網路協議,在建立連線後,它只允許Browser/UA(UserAgent)向WebServer發出請求資源後,WebServer才能返回相應的資料。而WebServer不能主動的推送資料給Browser/UA,當初這麼設計http協議也是有原因的,假設WebServer能主動的推送資料給Browser/UA,那Browser/UA就太容易受到攻擊,一些廣告商也會主動的把一些廣告資訊在不經意間強行的傳輸給客戶端,這不能不說是一個災難。那麼單向的http協議給現在的網站或Web應用程式開發帶來了哪些問題呢?

讓我們來看一個案例,現在假設我們想開發一個基於Web的應用程式去獲取當前Web伺服器的實時資料,例如股票的實時行情,火車票的剩餘票數等等,這就需要Browser/UA與WebServer端之間反覆的進行http通訊,Browser不斷的傳送Get請求,去獲取當前的實時資料。下面介紹幾種常見的方式:

1.     Polling


這種方式就是通過Browser/UA定時的向Web伺服器傳送http的Get請求,伺服器收到請求後,就把最新的資料發回給客戶端(Browser/UA),Browser/UA得到資料後,就將其顯示出來,然後再定期的重複這一過程。雖然這樣可以滿足需求,但是也仍然存在一些問題,例如在某段時間內Web伺服器端沒有更新的資料,但是Browser/UA仍然需要定時的傳送Get請求過來詢問,那麼Web伺服器就把以前的老資料再傳送過來,Browser/UA把這些沒有變化的資料再顯示出來,這樣顯然既浪費了網路頻寬,又浪費了CPU的利用率。如果說把Browser傳送Get請求的週期調大一些,就可以緩解這一問題,但是如果在Web伺服器端的資料更新很快時,這樣又不能保證Web應用程式獲取資料的實時性。

2.     Long Polling


上面介紹了Polling遇到的問題,現在介紹一下LongPolling,它是對Polling的一種改進。

Browser/UA傳送Get請求到Web伺服器,這時Web伺服器可以做兩件事情,第一,如果伺服器端有新的資料需要傳送,就立即把資料發回給Browser/UA,Browser/UA收到資料後,立即再發送Get請求給Web Server;第二,如果伺服器端沒有新的資料需要傳送,這裡與Polling方法不同的是,伺服器不是立即傳送迴應給Browser/UA,而是把這個請求保持住,等待有新的資料到來時,再來響應這個請求;當然了,如果伺服器的資料長期沒有更新,一段時間後,這個Get請求就會超時,Browser/UA收到超時訊息後,再立即傳送一個新的Get請求給伺服器。然後依次迴圈這個過程。

這種方式雖然在某種程度上減小了網路頻寬和CPU利用率等問題,但是仍然存在缺陷,例如假設伺服器端的資料更新速率較快,伺服器在傳送一個數據包給Browser後必須等待Browser的下一個Get請求到來,才能傳遞第二個更新的資料包給Browser,那麼這樣的話,Browser顯示實時資料最快的時間為2×RTT(往返時間),另外在網路擁塞的情況下,這個應該是不能讓使用者接受的。另外,由於http資料包的頭部資料量往往很大(通常有400多個位元組),但是真正被伺服器需要的資料卻很少(有時只有10個位元組左右),這樣的資料包在網路上週期性的傳輸,難免對網路頻寬是一種浪費。

通過上面的分析可知,要是在Browser能有一種新的網路協議,能支援客戶端和伺服器端的雙向通訊,而且協議的頭部又不那麼龐大就好了。WebSocket就是肩負這樣一個使命登上舞臺的。


WebSocket協議是一種雙向通訊協議,它建立在TCP之上,同http一樣通過TCP來傳輸資料,但是它和http最大的不同有兩點:1.WebSocket是一種雙向通訊協議,在建立連線後,WebSocket伺服器和Browser/UA都能主動的向對方傳送或接收資料,就像Socket一樣,不同的是WebSocket是一種建立在Web基礎上的一種簡單模擬Socket的協議;2.WebSocket需要通過握手連線,類似於TCP它也需要客戶端和伺服器端進行握手連線,連線成功後才能相互通訊。

下面是一個簡單的建立握手的時序圖:

這裡簡單說明一下WebSocket握手的過程。

當Web應用程式呼叫new WebSocket(url)介面時,Browser就開始了與地址為url的WebServer建立握手連線的過程。

1.     Browser與WebSocket伺服器通過TCP三次握手建立連線,如果這個建立連線失敗,那麼後面的過程就不會執行,Web應用程式將收到錯誤訊息通知。

2.     在TCP建立連線成功後,Browser/UA通過http協議傳送WebSocket支援的版本號,協議的字版本號,原始地址,主機地址等等一些列欄位給伺服器端。

例如:

  1. GET /chat HTTP/1.1  
  2. Host: server.example.com  
  3. Upgrade: websocket  
  4. Connection: Upgrade  
  5. Sec-WebSocket-Key:dGhlIHNhbXBsZSBub25jZQ==  
  6. Origin: http://example.com  
  7. Sec-WebSocket-Protocol: chat,superchat  
  8. Sec-WebSocket-Version: 13  

3.     WebSocket伺服器收到Browser/UA傳送來的握手請求後,如果資料包資料和格式正確,客戶端和伺服器端的協議版本號匹配等等,就接受本次握手連線,並給出相應的資料回覆,同樣回覆的資料包也是採用http協議傳輸。

  1. HTTP/1.1 101 Switching Protocols  
  2. Upgrade: websocket  
  3. Connection: Upgrade  
  4. Sec-WebSocket-Accept:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  
  5. Sec-WebSocket-Protocol: chat  

4.     Browser收到伺服器回覆的資料包後,如果資料包內容、格式都沒有問題的話,就表示本次連線成功,觸發onopen訊息,此時Web開發者就可以在此時通過send介面想伺服器傳送資料。否則,握手連線失敗,Web應用程式會收到onerror訊息,並且能知道連線失敗的原因。

WebSocket與http協議一樣都是基於TCP的,所以他們都是可靠的協議,Web開發者呼叫的WebSocket的send函式在browser的實現中最終都是通過TCP的系統介面進行傳輸的。WebSocket和Http協議一樣都屬於應用層的協議,那麼他們之間有沒有什麼關係呢?答案是肯定的,WebSocket在建立握手連線時,資料是通過http協議傳輸的,正如我們上一節所看到的“GET/chat HTTP/1.1”,這裡面用到的只是http協議一些簡單的欄位。但是在建立連線之後,真正的資料傳輸階段是不需要http協議參與的。

具體關係可以參考下圖:

WebSocket(5)-- WebSocket Server

如果要搭建一個Web伺服器,我們會有很多選擇,市場上也有很多成熟的產品供我們應用,比如開源的Apache,安裝後只需簡單的配置(或者預設配置)就可以工作了。但是如果想搭建一個WebSocket伺服器就沒有那麼輕鬆了,因為WebSocket是一種新的通訊協議,目前還是草案,沒有成為標準,市場上也沒有成熟的WebSocket伺服器或者Library實現WebSocket協議,我們就必須自己動手寫程式碼去解析和組裝WebSocket的資料包。要這樣完成一個WebSocket伺服器,估計所有的人都想放棄,幸好的是市場上有幾款比較好的開源庫供我們使用,比如PyWebSocket,WebSocket-Node, LibWebSockets等等,這些庫檔案已經實現了WebSocket資料包的封裝和解析,我們可以呼叫這些介面,這在很大程度上減少了我們的工作量。

下面就簡單介紹一下這些開源的庫檔案。

1.     PyWebSocket

PyWebSocket採用Python語言編寫,可以很好的跨平臺,擴充套件起來也比較簡單,目前WebKit採用它搭建WebSocket伺服器來做LayoutTest。

我們可以獲取原始碼通過下面的命令

svn checkouthttp://pywebsocket.googlecode.com/svn/trunk/ pywebsocket-read-only

2.     WebSocket-Node

WebSocket-Node採用JavaScript語言編寫,這個庫是建立在nodejs之上的,對於熟悉JavaScript的朋友可參考一下,另外Html5和Web應用程式受歡迎的程度越來越高,nodejs也正受到廣泛的關注。

我們可以從下面的連線中獲取原始碼

3.     LibWebSockets

LibWebSockets採用C/C++語言編寫,可定製化的力度更大,從TCP監聽開始到封包的完成我們都可以參與程式設計。

我們可以從下面的命令獲取原始碼

git clone git://git.warmcat.com/libwebsockets