上一篇文章中使用了Ajax long polling實現了一個簡單的聊天程序,對於web實時通信,今天就來試用一下基於WebSocket的長連接方式。
WebSocket簡介
為了增強web通信的功能,在HTML5中,提供了WebSocket,它不僅僅是一種web通信方式,也是一種應用層協議。
WebSocket提供了客戶端和服務端之間的雙全工跨域通信,通過客戶端和服務端之間建立WebSocket連接(實際上是TCP連接,后面會看到),在同一時刻能夠實現客戶端到服務器和服務器到客戶端的數據發送。
Ajax long polling是一種客戶端去服務端拉取數據的方式,而WebSocket則能真正實現服務端主動向客戶端推送數據。下圖形象的展示了WebSocket的工作方式。
對於WebSocket這種新的應用層協議,在實現應用的時候,客戶端和服務端都需要遵循WebSocket協議,關於更多的WebSocket內容,請參考websocket.org。
實現
首先,還是先看看通過WebSocket實現的聊天程序的代碼以及效果,然后再看WebSocket工作方式相關的內容。
客戶端
因為並不是所有版本的瀏覽器都能夠支持WebSocket,所以例子中通過下面代碼來檢測當前瀏覽器是否支持WebSocket。
if(window.WebSocket){ //support WebSocket, more code here } else{ alert("WebSocket was not supported"); }
對於客戶端,主要就是updater這個對象,該對象會創建並維護了一個WebSocket對象,通過這個WebSocket對象就可以跟服務端進行交互(收取或發送消息)。
var updater = { socket: null, start: function() { var url = "ws://" + location.host + "/chatsocket"; updater.socket = new WebSocket(url); updater.socket.onopen = function(event) { } updater.socket.onclose = function(event) { alert("server socket closed"); } updater.socket.onmessage = function(event) { updater.showMessage(event.data); } }, showMessage: function(message) { console.log(message); $("#inbox").append(message); $("#message").val(""); } };
服務端
對於服務端,這次使用了gevent-websocket這個庫,可以很方便的通過pip進行安裝。
服務端通過MessageBuffer這個類來管理所有的消息,以及所有的WebSocket client。由於WebSocket是一種長連接的方式,所以可以很容易的統計出當前在線的client的數量。
class MessageBuffer(object): def __init__(self, cache_size = 200): self.cache = [] self.cache_size = cache_size self.clients = [] def new_message(self, msg): self.cache.append(msg) if len(self.cache) > self.cache_size: self.cache = self.cache[-self.cache_size:] def update_clients(self, msg): for client in self.clients: client.send(msg)
跟上次相比,使用WebSocket之后,服務器代碼更加簡潔了。當客戶端發起"/chatsocket"請求后,服務器就會跟客戶端建立連接,並將客戶端加入"messageBuffer.clients"列表中;當客戶端斷開連接,就會將客戶端從"messageBuffer.clients"列表中移除。
當服務器收到新消息后,就會通過"messageBuffer.update_clients"方法,將新消息推送到所有的客戶端。
def application(env, start_response): # visit the main page if env['PATH_INFO'] == '/': # some code to load main page here elif env['PATH_INFO'] == '/chatsocket': ws = env["wsgi.websocket"] messageBuffer.clients.append(ws) print "new client join, total client count %d" %len(messageBuffer.clients) while True: message = ws.receive() if message is None: messageBuffer.clients.remove(ws) print "client leave, total client count %d" %len(messageBuffer.clients) break print "Got message: %s" %message message = "<div>{0}</div>".format(message) messageBuffer.new_message(message) messageBuffer.update_clients(message)
運行效果
下面就是代碼的運行效果。
由於WebSocket是長連接的方式,所以可以方便的統計當前在線客戶端數量。
當關閉服務器的時候,客戶端也可以檢測到連接的斷開。
WebSocket工作機制
下面就從工作機制來看看WebSocket是怎么為應用提供長連接服務的。
WebSocket連接建立
雖然WebSocket是一種新的應用層協議,但是它的工作也是要依賴於http協議的。
通過Wireshark,我們可以抓到下面的數據包。
這兩個數據包就是建立WebSocket連接的握手過程(WebSocket Protocol handshake):
1. 客戶端的WebSocket實例綁定一個需要連接到的服務器地址,當客戶端連接服務端的時候,會向服務端發送一個類似下面的HTTP GET請求
在上面的請求中有一個Upgrade首部,這個首部是告訴服務端需要將通信協議切換到WebSocket
2. 在收到帶有"Upgrade: websocket"首部的請求后,如果服務端支持WebSocket協議,那么它就會將自己的通信協議切換到WebSocket,同時發給客戶端類似以下的響應報文頭。
響應報文的狀態碼為101,表示同意客戶端協議轉換請求,並將它轉換為WebSocket協議。到此,客戶端和服務端的WebSocket連接就建立成功了,以后的通信就是基於WebSocket連接了。
WebSocket連接保活
WebSocket底層的工作/實現都是基於TCP協議,所以連接的保活機制是跟TCP一樣的,就是通過"TCP Keep-Alive"心跳包來保證連接始終處於有效狀態。
WebSocket連接關閉
對於WebSocket連接的關閉,也是主動關閉端發送"FIN, ACK"數據包來完成關閉的。
總結
本文簡單介紹了HTML5中的WebSocket協議,並通過WebSocket實現了一個簡單的聊天程序。
WebSocket能在客戶端和服務端建立長連接,並提供全雙工的數據傳輸,提供了服務器推送數據的模式。
跟Ajax long polling方式進行對比,這種服務器主動推送數據的方式更加適合實時數據交互應用。
Ps:
通過此處可以下載例子的源碼。