初試WebSocket構建聊天程序


上一篇文章中使用了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:

通過此處可以下載例子的源碼。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM