上一篇文章中使用了Ajax long polling實現了一個簡單的聊天程序,對於web實時通訊,今天就來試用一下基於WebSocket的長鏈接方式。html
爲了加強web通訊的功能,在HTML5中,提供了WebSocket,它不單單是一種web通訊方式,也是一種應用層協議。web
WebSocket提供了客戶端和服務端之間的雙全工跨域通訊,經過客戶端和服務端之間創建WebSocket鏈接(其實是TCP鏈接,後面會看到),在同一時刻可以實現客戶端到服務器和服務器到客戶端的數據發送。跨域
Ajax long polling是一種客戶端去服務端拉取數據的方式,而WebSocket則能真正實現服務端主動向客戶端推送數據。下圖形象的展現了WebSocket的工做方式。瀏覽器
對於WebSocket這種新的應用層協議,在實現應用的時候,客戶端和服務端都須要遵循WebSocket協議,關於更多的WebSocket內容,請參考websocket.org。服務器
首先,仍是先看看經過WebSocket實現的聊天程序的代碼以及效果,而後再看WebSocket工做方式相關的內容。websocket
由於並非全部版本的瀏覽器都可以支持WebSocket,因此例子中經過下面代碼來檢測當前瀏覽器是否支持WebSocket。app
if(window.WebSocket){ //support WebSocket, more code here } else{ alert("WebSocket was not supported"); }
對於客戶端,主要就是updater這個對象,該對象會建立並維護了一個WebSocket對象,經過這個WebSocket對象就能夠跟服務端進行交互(收取或發送消息)。socket
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進行安裝。url
服務端經過MessageBuffer這個類來管理全部的消息,以及全部的WebSocket client。因爲WebSocket是一種長鏈接的方式,因此能夠很容易的統計出當前在線的client的數量。spa
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是一種新的應用層協議,可是它的工做也是要依賴於http協議的。
經過Wireshark,咱們能夠抓到下面的數據包。
這兩個數據包就是創建WebSocket鏈接的握手過程(WebSocket Protocol handshake):
1. 客戶端的WebSocket實例綁定一個須要鏈接到的服務器地址,當客戶端鏈接服務端的時候,會向服務端發送一個相似下面的HTTP GET請求
在上面的請求中有一個Upgrade首部,這個首部是告訴服務端須要將通訊協議切換到WebSocket
2. 在收到帶有"Upgrade: websocket"首部的請求後,若是服務端支持WebSocket協議,那麼它就會將本身的通訊協議切換到WebSocket,同時發給客戶端相似如下的響應報文頭。
響應報文的狀態碼爲101,表示贊成客戶端協議轉換請求,並將它轉換爲WebSocket協議。到此,客戶端和服務端的WebSocket鏈接就創建成功了,之後的通訊就是基於WebSocket鏈接了。
WebSocket底層的工做/實現都是基於TCP協議,因此鏈接的保活機制是跟TCP同樣的,就是經過"TCP Keep-Alive"心跳包來保證鏈接始終處於有效狀態。
對於WebSocket鏈接的關閉,也是主動關閉端發送"FIN, ACK"數據包來完成關閉的。
本文簡單介紹了HTML5中的WebSocket協議,並經過WebSocket實現了一個簡單的聊天程序。
WebSocket能在客戶端和服務端創建長鏈接,並提供全雙工的數據傳輸,提供了服務器推送數據的模式。
跟Ajax long polling方式進行對比,這種服務器主動推送數據的方式更加適合實時數據交互應用。
Ps:
經過此處能夠下載例子的源碼。