Python實現同時相容老版和新版Socket協議的一個簡單WebSocket伺服器

來源:互聯網
上載者:User
最近在做的一個項目中需要使用到HTML5中引入的WebSocket技術,本來以為應該很容易就能搞定,誰知道在真正上手開發了以後才發現有很多麻煩的地方,雖然我們是一個以前端開發和設計見長的團隊,而且作為一個二手程式猿又長期不被待見,但是為了讓有同樣需求的朋友少走些彎路,我還是決定把實現方法貼在這個地方。

關於WebSocket的基本概念,維基百科上解釋的很清楚,而且網上也能搜出來一大把,這裡就略過不表,直接進入正題。

這次的問題首先有一個前提,就是得用Python來實現這個伺服器,如果對具體語言沒有限制的話,推薦大家首選Node.js的一個第三方庫:Socket.IO,非常好用,10分鐘不打針不吃藥搞定WebSocket Server,而且用JS來寫後端,相信也能對上很多文藝開發人員的胃口。

但是如果選擇用Python,google搜尋的結果幾乎都不能用,最要命的問題是,WebSocket協議本身還是一個草案,所以不同瀏覽器支援的協議版本有所不同,Safari 5.1支援的是老版本協議Hybi-02,Chrome 15以及Firefox 8.0支援的是新版本協議Hybi-10,老版本協議和新版本協議在建立通訊的握手方法還有資料轉送的格式要求上都有所不同,導致網上大多數實現方式只能適用於Safari瀏覽器,並且Safari和C&F瀏覽器之間無法互相通訊。

首先第一步需要解釋的是新、舊版本WebSocket協議的握手方式。我們先來看看三個不同瀏覽器發送的握手資料的結構:

Chrome:
複製代碼 代碼如下:


GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 127.0.0.1:1337
Sec-WebSocket-Origin: http://127.0.0.1:8000
Sec-WebSocket-Key: erWJbDVAlYnHvHNulgrW8Q==
Sec-WebSocket-Version: 8
Cookie: csrftoken=xxxxxx; sessionid=xxxxx


Firefox:
複製代碼 代碼如下:

GET / HTTP/1.1
Host: 127.0.0.1:1337
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:8.0) Gecko/20100101 Firefox/8.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip, deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Connection: keep-alive, Upgrade
Sec-WebSocket-Version: 8
Sec-WebSocket-Origin: http://127.0.0.1:8000
Sec-WebSocket-Key: 1t3F81iAxNIZE2TxqWv+8A==
Cookie: xxx
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket


Safari:
複製代碼 代碼如下:

GET / HTTP/1.1
Upgrade: WebSocket
Connection: Upgrade
Host: 127.0.0.1:1337
Origin: http://127.0.0.1:8000
Cookie: sessionid=xxxx; calView=day; dayCurrentDate=1314288000000
Sec-WebSocket-Key1: cV`p1* 42#7 ^9}_ 647 08{
Sec-WebSocket-Key2: O8 415 8x37R A8 4
;"######

可以看出,Chrome和Firefox實現的是新版協議,因此只傳輸了一個”Sec-WebSocket-Key”頭以供服務端產生握手Token,但是遵循老版本的Safari的資料中有兩個Key:”Sec-WebSocket-Key1″和”Sec-WebSocket-Key2″,因此服務端在產生握手Token的時候,需要做一次判斷。先來看使用老版本協議的Safari,Token產生演算法如下:

取出Sec-WebSocket-Key1中的所有數字字元形成一個數值,這裡是1427964708,然後除以Key1中的空格數目,這裡好像是6個空格,得到一個數值,保留該數值整數位,得到數值N1;對Sec-WebSocket-Key2如法炮製,得到第二個整數N2;把N1和N2按照Big-Endian字元序列串連起來,然後再與另外一個Key3串連,得到一個原始序列ser_key。那麼Key3是什麼呢?大家可以看到在Safari發送過來的握手請求最後,有一個8位元組的奇怪的字串“;”######”,這個就是Key3。回到ser_key,對這個原始序列做md5算出一個16位元組長的digest,這就是老版本協議需要的token,然後將這個token附在握手訊息的最後發送回Client,即可完成握手。

新版協議產生Token的方法比較簡單:首先把Sec-WebSocket-Key和一串固定的UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”做拼接,然後對這個拼接後的字串做SHA1加密,得到digest以後,做一次base64編碼,即可獲得Token。

另外需要注意的是,新版本和老版本握手協議回傳給Client的資料結構有所不同,在附件中的server源碼中寫得很清楚了,看看就能明白。
完成握手只是WebSocket Server的一半功能,現在只能保證這個Server能夠和兩個版本的瀏覽器建立連結了,但是如果試著把Chrome中的訊息發送給Safari,會發現Safari無法接收。導致這個結果的原因,是因為兩個版本的協議的Data Framing結構不同,也即是在握手建立串連後,Client發送和接收的資料結構都不一樣。

首先第一步需要擷取不同版本協議下Client發送過來的未經處理資料。老版本協議比較簡單,實際上就是在未經處理資料前加了個'\x00′,在最後面加上了一個'\xFF',所以如果Safari的Client發送一個字串'test',實際上WebSocket Server收到的資料是:'x00test\xFF',所以只需要剝離掉首尾那兩個字元就可以了。

比較麻煩的是新版本協議的資料,按照新版draft的解釋,Chrome和Firefox發過來的資料報文由以下幾個部分組成:首先是一個固定的位元組(1000 0001或是1000 0002),這個位元組可以不用理會。麻煩的是第二個位元組,這裡假設第二個位元組是1011 1100,首先這個位元組的第一位肯定是1,表示這是一個”masked”位,剩下的7個0/1位能夠計算出一個數值,比如這裡剩下的是 011 1100,計算出來就是60,這個值需要做如下判斷:

如果這個值介於0000 0000 和 0111 1101 (0 ~ 125) 之間,那麼這個值就代表了實際資料的長度;如果這個數值剛好等於0111 1110 (126),那麼接下來的2個位元組才代表真實資料長度;如果這個數值剛好等於0111 1111 (127),那麼接下來的8個位元組代表資料長度。

有了這個判斷之後,能夠知道代表資料長度的位元組在第幾位結束,比如我們舉得例子60,這個值介於0~125之間,所以第二個位元組本身就代表了未經處理資料的長度了(60個位元組),所以從第三個位元組開始,我們能抓出4個位元組來,這一串位元組叫做 “masks” (掩碼?),掩碼之後的資料,就是實際的data…的兄弟了。說是兄弟,是因為這個資料是實際data根據掩碼做過一次位元運算後得到的,獲得原始data的方法是,將兄弟資料的每一位x,和掩碼的第i%4位做xor運算,其中i是x在兄弟資料中的索引。看得眼花是吧,看看下面這個程式碼片段也許就能明白了:

複製代碼 代碼如下:


def send_data(raw_str):
back_str = []

back_str.append('\x81')
data_length = len(raw_str)

if data_length < 125:
back_str.append(chr(data_length))
else:
back_str.append(chr(126))
back_str.append(chr(data_length >> 8))
back_str.append(chr(data_length & 0xFF))

back_str = "".join(back_str) + raw_str

這樣產生的back_str,就能夠發送給使用新版協議的Chrome或是Firefox了。

至此,這個簡單的WebSocket Server就完成了,能夠同時相容老版協議和新版協議的Socket串連,以及不同版本之間的資料轉送。該Server的源碼請點擊這裡下載,需要注意的是裡面用到了twisted架構來跑TCP服務,代碼寫得不怎麼樣,僅供大家參考。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.