天天看點

python websocket學習使用Python通過websocket與js用戶端通信示例分析

前言

進一步簡述

This module is tested on Python 2.7 and Python 3.x.

Type "python setup.py install" or "pip install websocket-client" to install.

Caution!

from v0.16.0, we can install by "pip install websocket-client" for python 3.

This module depend on

six

backports.ssl_match_hostname for Python 2.x

這裡,介紹如何使用 Python 與前端 js 進行通信。

websocket 使用 HTTP 協定完成握手之後,不通過 HTTP 直接進行 websocket 通信。

于是,使用 websocket 大緻兩個步驟:使用 HTTP 握手,通信。

js 處理 websocket 要使用 ws 子產品; Python 處理則使用 socket 子產品建立 TCP 連接配接即可,比一般的 socket ,隻多一個握手以及資料處理的步驟。

包格式

js 用戶端先向伺服器端 python 發送握手包,格式如下:

<code>GET /chat HTTP/1.1</code>

<code>Host: server.example.com</code>

<code>Upgrade: websocket</code>

<code>Connection: Upgrade</code>

<code>Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==</code>

<code>Sec-WebSocket-Protocol: chat, superchat</code>

<code>Sec-WebSocket-Version: 13</code>

伺服器回應包格式:

<code>HTTP/1.1 101 Switching Protocols</code>

<code>Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=</code>

<code>Sec-WebSocket-Protocol: chat</code>

其中, Sec-WebSocket-Key 是随機的,伺服器用這些資料構造一個 SHA-1 資訊摘要。

方法為: key+migic , SHA-1  加密, base-64 加密

Python 中的處理代碼:

<code>MAGIC_STRING</code><code>=</code> <code>'258EAFA5-E914-47DA-95CA-C5AB0DC85B11'</code>

<code>res_key</code><code>=</code> <code>base64.b64encode(hashlib.sha1(sec_key</code><code>+</code> <code>MAGIC_STRING).digest())</code>

握手完整代碼

js 端

js 中有處理 websocket 的類,初始化後自動發送握手包,如下:

var socket = new WebSocket('ws://localhost:3368');

Python 端

Python 用 socket 接受得到握手字元串,處理後發送

<code>HOST</code><code>=</code> <code>'localhost'</code>

<code>PORT</code><code>=</code> <code>3368</code>

<code>HANDSHAKE_STRING</code><code>=</code> <code>"HTTP/1.1 101 Switching Protocols\r\n"</code> <code>\</code>

<code>      </code><code>"Upgrade:websocket\r\n"</code> <code>\</code>

<code>      </code><code>"Connection: Upgrade\r\n"</code> <code>\</code>

<code>      </code><code>"Sec-WebSocket-Accept: {1}\r\n"</code> <code>\</code>

<code>      </code><code>"WebSocket-Protocol:chat\r\n\r\n"</code>

<code> </code> 

<code>def</code> <code>handshake(con):</code>

<code>#con為用socket,accept()得到的socket</code>

<code> </code><code>headers</code><code>=</code> <code>{}</code>

<code> </code><code>shake</code><code>=</code> <code>con.recv(</code><code>1024</code><code>)</code>

<code> </code><code>if</code> <code>not</code> <code>len</code><code>(shake):</code>

<code>  </code><code>return</code> <code>False</code>

<code> </code><code>header, data</code><code>=</code> <code>shake.split(</code><code>'\r\n\r\n'</code><code>,</code><code>1</code><code>)</code>

<code> </code><code>for</code> <code>line</code><code>in</code> <code>header.split(</code><code>'\r\n'</code><code>)[</code><code>1</code><code>:]:</code>

<code>  </code><code>key, val</code><code>=</code> <code>line.split(</code><code>': '</code><code>,</code><code>1</code><code>)</code>

<code>  </code><code>headers[key]</code><code>=</code> <code>val</code>

<code> </code><code>if</code> <code>'Sec-WebSocket-Key'</code> <code>not</code> <code>in</code> <code>headers:</code>

<code>  </code><code>print</code> <code>(</code><code>'This socket is not websocket, client close.'</code><code>)</code>

<code>  </code><code>con.close()</code>

<code> </code><code>sec_key</code><code>=</code> <code>headers[</code><code>'Sec-WebSocket-Key'</code><code>]</code>

<code> </code><code>res_key</code><code>=</code> <code>base64.b64encode(hashlib.sha1(sec_key</code><code>+</code> <code>MAGIC_STRING).digest())</code>

<code> </code><code>str_handshake</code><code>=</code> <code>HANDSHAKE_STRING.replace(</code><code>'{1}'</code><code>, res_key).replace(</code><code>'{2}'</code><code>, HOST</code><code>+</code> <code>':'</code> <code>+</code> <code>str</code><code>(PORT))</code>

<code> </code><code>print</code> <code>str_handshake</code>

<code> </code><code>con.send(str_handshake)</code>

<code>return</code> <code>True</code>

通信

不同版本的浏覽器定義的資料幀格式不同, Python 發送和接收時都要處理得到符合格式的資料包,才能通信。

Python 接收

Python 接收到浏覽器發來的資料,要解析後才能得到其中的有用資料。

固定位元組:

( 1000 0001 或是 1000 0002 )這裡沒用,忽略

包長度位元組:

第一位肯定是 1 ,忽略。剩下 7 個位可以得到一個整數 (0 ~ 127) ,其中

( 1-125 )表此位元組為長度位元組,大小即為長度;

(126)表接下來的兩個位元組才是長度;

(127)表接下來的八個位元組才是長度;

用這種變長的方式表示資料長度,節省資料位。

mark 掩碼:

mark 掩碼為包長之後的 4 個位元組,之後的兄弟資料要與 mark 掩碼做運算才能得到真實的資料。

兄弟資料:

得到真實資料的方法:将兄弟資料的每一位 x ,和掩碼的第 i%4 位做 xor 運算,其中 i 是 x 在兄弟資料中的索引。

完整代碼

<code>def</code> <code>recv_data(</code><code>self</code><code>, num):</code>

<code> </code><code>try</code><code>:</code>

<code>  </code><code>all_data</code><code>=</code> <code>self</code><code>.con.recv(num)</code>

<code>  </code><code>if</code> <code>not</code> <code>len</code><code>(all_data):</code>

<code>   </code><code>return</code> <code>False</code>

<code> </code><code>except</code><code>:</code>

<code> </code><code>else</code><code>:</code>

<code>  </code><code>code_len</code><code>=</code> <code>ord</code><code>(all_data[</code><code>1</code><code>]) &amp;</code><code>127</code>

<code>  </code><code>if</code> <code>code_len</code><code>=</code><code>=</code> <code>126</code><code>:</code>

<code>   </code><code>masks</code><code>=</code> <code>all_data[</code><code>4</code><code>:</code><code>8</code><code>]</code>

<code>   </code><code>data</code><code>=</code> <code>all_data[</code><code>8</code><code>:]</code>

<code>  </code><code>elif</code> <code>code_len</code><code>=</code><code>=</code> <code>127</code><code>:</code>

<code>   </code><code>masks</code><code>=</code> <code>all_data[</code><code>10</code><code>:</code><code>14</code><code>]</code>

<code>   </code><code>data</code><code>=</code> <code>all_data[</code><code>14</code><code>:]</code>

<code>  </code><code>else</code><code>:</code>

<code>   </code><code>masks</code><code>=</code> <code>all_data[</code><code>2</code><code>:</code><code>6</code><code>]</code>

<code>   </code><code>data</code><code>=</code> <code>all_data[</code><code>6</code><code>:]</code>

<code>  </code><code>raw_str</code><code>=</code> <code>""</code>

<code>  </code><code>i</code><code>=</code> <code>0</code>

<code>  </code><code>for</code> <code>d</code><code>in</code> <code>data:</code>

<code>   </code><code>raw_str</code><code>+</code><code>=</code> <code>chr</code><code>(</code><code>ord</code><code>(d) ^</code><code>ord</code><code>(masks[i</code><code>%</code> <code>4</code><code>]))</code>

<code>   </code><code>i</code><code>+</code><code>=</code> <code>1</code>

<code>  </code><code>return</code> <code>raw_str</code>

js 端的 ws 對象,通過 ws.send(str) 即可發送

ws.send(str)

Python 發送

Python 要包資料發送,也需要處理

固定位元組:固定的 1000 0001( ‘ \x81 ′ )

包長:根據發送資料長度是否超過 125 , 0xFFFF(65535) 來生成 1 個或 3 個或 9 個位元組,來代表資料長度。

<code>def</code> <code>send_data(</code><code>self</code><code>, data):</code>

<code> </code><code>if</code> <code>data:</code>

<code>  </code><code>data</code><code>=</code> <code>str</code><code>(data)</code>

<code> </code><code>token</code><code>=</code> <code>"\x81"</code>

<code> </code><code>length</code><code>=</code> <code>len</code><code>(data)</code>

<code> </code><code>if</code> <code>length &lt;</code><code>126</code><code>:</code>

<code>  </code><code>token</code><code>+</code><code>=</code> <code>struct.pack(</code><code>"B"</code><code>, length)</code>

<code> </code><code>elif</code> <code>length &lt;</code><code>=</code> <code>0xFFFF</code><code>:</code>

<code>  </code><code>token</code><code>+</code><code>=</code> <code>struct.pack(</code><code>"!BH"</code><code>,</code><code>126</code><code>, length)</code>

<code>  </code><code>token</code><code>+</code><code>=</code> <code>struct.pack(</code><code>"!BQ"</code><code>,</code><code>127</code><code>, length)</code>

<code> </code><code>#struct為Python中處理二進制數的子產品,二進制流為C,或網絡流的形式。</code>

<code> </code><code>data</code><code>=</code> <code>'%s%s'</code> <code>%</code> <code>(token, data)</code>

<code> </code><code>self</code><code>.con.send(data)</code>

<code> </code><code>return</code> <code>True</code>

js 端通過回調函數 ws.onmessage() 接受資料

<code>ws.onmessage =</code><code>function</code><code>(result,nTime){</code>

<code>alert(</code><code>"從服務端收到的資料:"</code><code>);</code>

<code>alert(</code><code>"最近一次發送資料到現在接收一共使用時間:"</code> <code>+ nTime);</code>

<code>console.log(result);</code>

<code>}</code>

最終代碼

Python服務端

js客戶 端

技術改變世界!

--狂詩絕劍