初試WebSocket構建聊天程序

上一篇文章中使用了Ajax long polling實現了一個簡單的聊天程序,對於web實時通訊,今天就來試用一下基於WebSocket的長鏈接方式。html

WebSocket簡介

爲了加強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是怎麼爲應用提供長鏈接服務的。

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:

經過此處能夠下載例子的源碼。

相關文章
相關標籤/搜索