Python3+WebSockets實現WebSocket通信


一、說明

1.1 背景說明

前段時間同事說雲平台通信使用了個websocket的東西,今天抽空來看一下具體是怎么個通信過程。

從形式上看,websocket是一個應用層協議,socket是數據鏈路層、網絡層、傳輸層的抽像;從應用場合上看,websocket可以使用javascript實現,而socket不能用javascript實現(真不能嗎?我不太確定);從實際效果上看,和一般的socket連接用起來沒什么區別。

我們知道http是短連接的,反復建立和銷毀連接比較耗費資源,另外http協議經常頭部內容比主體內容還長也比較浪費資源;websocket可以認為就是一個內容使用載荷固定格式的socket長連接。

websocket基本協議格式如下,更多說明見RFC 6455

 

1.2 環境說明

當前環境我使用Python3+WebSockets庫,WebSockets直接使用pip安裝即可:

pip install websockets

 

二、代碼實現

長連接是有狀態的,所以一般在且只在最開始進行一次身份認證,而后通信過程不需要認證信息。我們這里實現一個簡單的用戶名密碼認證過程。長連接更多內容可參考“連接與短連接的安全差異討論 ”。

另外,注意把代碼中的ip改成自己的。

 

2.1 python服務端代碼

import asyncio
import websockets

# 檢測客戶端權限,用戶名密碼通過才能退出循環
async def check_permit(websocket):
    while True:
        recv_str = await websocket.recv()
        cred_dict = recv_str.split(":")
        if cred_dict[0] == "admin" and cred_dict[1] == "123456":
            response_str = "congratulation, you have connect with server\r\nnow, you can do something else"
            await websocket.send(response_str)
            return True
        else:
            response_str = "sorry, the username or password is wrong, please submit again"
            await websocket.send(response_str)

# 接收客戶端消息並處理,這里只是簡單把客戶端發來的返回回去
async def recv_msg(websocket):
    while True:
        recv_text = await websocket.recv()
        response_text = f"your submit context: {recv_text}"
        await websocket.send(response_text)

# 服務器端主邏輯
# websocket和path是該函數被回調時自動傳過來的,不需要自己傳
async def main_logic(websocket, path):
    await check_permit(websocket)

    await recv_msg(websocket)

# 把ip換成自己本地的ip
start_server = websockets.serve(main_logic, '10.10.6.91', 5678)
# 如果要給被回調的main_logic傳遞自定義參數,可使用以下形式
# 一、修改回調形式
# import functools
# start_server = websockets.serve(functools.partial(main_logic, other_param="test_value"), '10.10.6.91', 5678)
# 修改被回調函數定義,增加相應參數
# async def main_logic(websocket, path, other_param)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

 

2.2 python版客戶端代碼

import asyncio
import websockets

# 向服務器端認證,用戶名密碼通過才能退出循環
async def auth_system(websocket):
    while True:
        cred_text = input("please enter your username and password: ")
        await websocket.send(cred_text)
        response_str = await websocket.recv()
        if "congratulation" in response_str:
            return True

# 向服務器端發送認證后的消息
async def send_msg(websocket):
    while True:
        _text = input("please enter your context: ")
        if _text == "exit":
            print(f'you have enter "exit", goodbye')
            await websocket.close(reason="user exit")
            return False
        await websocket.send(_text)
        recv_text = await websocket.recv()
        print(f"{recv_text}")

# 客戶端主邏輯
async def main_logic():
    async with websockets.connect('ws://10.10.6.91:5678') as websocket:
        await auth_system(websocket)

        await send_msg(websocket)

asyncio.get_event_loop().run_until_complete(main_logic())

 

2.3 html版客戶端代碼

html版客戶端代碼,只能通過回調函數接收服務端返回的數據,不能主動接收,感覺怪怪的。

<!DOCTYPE HTML>
<html>
   <head>
   <meta charset="utf-8">
   <title>websocket通信客戶端</title>
       <script type="text/javascript">
         function WebSocketTest()
         {
            if ("WebSocket" in window)
            {
               // 打開一個 web socket
               var ws = new WebSocket("ws://10.10.6.91:5678");
               
               // 連接建立后的回調函數
               ws.onopen = function()
               {
               // Web Socket 已連接上,使用 send() 方法發送數據
                  ws.send("admin:123456");
                  alert("正在發送:admin:123456");
               };
               
               // 接收到服務器消息后的回調函數
               ws.onmessage = function (evt) 
               { 
                  var received_msg = evt.data;
                  if (received_msg.indexOf("sorry") == -1) {
                    alert("收到消息:"+received_msg);
                  }
                  
               };
               
               // 連接關閉后的回調函數
               ws.onclose = function()
               { 
                  // 關閉 websocket
                  alert("連接已關閉..."); 
               };
            }
            else
            {
               // 瀏覽器不支持 WebSocket
               alert("您的瀏覽器不支持 WebSocket!");
            }
         }
         
      </script>
   </head>

   <body onload="WebSocketTest()">

   </body>
</html>
View Code

 

三、通信數據包截獲及通信過程分析

以下數據包其於上邊的python服務端和html版客戶端,再次強調注意把代碼中的ip改成自己電腦當前的ip。

 

3.1 wireshark通信數據包截獲及通信過程分析

wireshark攔截數據包后可使用過濾器表達式“websocket”進行過濾:

我們追蹤數據流可以更清晰地看清websocket的通信過程,可以看到先是用http完成了建立連接,然后切換到websocket協議。

 再看具體數據流也確實如此,先用兩個http包建立連接,而后是websocket通信(問題是不清楚websocket內容是怎么編碼的,有些就顯示不了原始內容)

 

3.2 burpsuite通信數據包截獲及通信過程分析

和正常配置代理即可,burpsuite能識別和攔截websocket數據包,如下圖:

不過和一般http請求有區別的是,websocket請求會被單獨匯總到“WebSockets history”選項卡,而不是和http請求混在“HTTP history”選項卡。

 

3.3 開發者工具通信數據包截獲及通信過程分析

Firefox開發者工具只能看到建立websocket連接的兩個http數據包,沒看到怎么查看具體傳輸內容,Chrome開發者工具Frames選項卡可以,如下:

 

參考:

https://websockets.readthedocs.io/en/stable/intro.html

https://www.runoob.com/html/html5-websocket.html

https://github.com/aaugustin/websockets/issues/271


免責聲明!

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



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