1. 為什么使用 Websocket ?
1.1 websocket 協(xié)議簡介
Websocket協(xié)議是對http的改進,可以實現(xiàn)client 與 server之間的雙向通信; websocket連接一旦建立就始終保持,直到client或server 中斷連接,彌補了http無法保持長連接的不足,方便了客戶端應用與服務器之間實時通信。
適用場景
- html頁面實時更新, 客戶端的html頁面內,用` javascript` 與 server 建立websocket連接,實現(xiàn)頁面內容的實時更新。Websocket 非常適合網(wǎng)頁游戲、聊天、證券交易等實時應用。
- 要求保持長連接的實時通信的應用場景。 如基于位置的服務應用,物聯(lián)網(wǎng),多方協(xié)作軟件,在線教育,帶社交屬性的手機APP等。
實時更新數(shù)據(jù)場景,為什么不使用AJAX?
AJAX 采用http, 如果要實時更新頁面,則需要不斷地發(fā)送http 請求,無論是否有數(shù)據(jù)更新,產生大量冗余通信流量。而websocket是長連接雙向通信,有數(shù)據(jù)更新時,服務器向客戶機發(fā)送通知。
1.2 基本原理
基于TCP,一次握手就能建立連接,支持雙向通信,可保持長連接。
WebSocket 握手請求消息示例::
GET /chat HTTP/1.1
Host: normal-website.com
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: wDqumtseNBJdhkihL6PW7w==
Connection: keep-alive, Upgrade
Cookie: session=KOsEJNuflw4Rd9BDNrVmvwBF9rEijeE2
Upgrade: websocket
如果 Server 接收連接,返回響應
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: 0FFP+2nmNIf/h+4BP36k9uzrYGk=
響應碼為101,表示切換為websocket 協(xié)議。
websocket 已得到主流瀏覽器,各編程語言的廣泛支持,基本都提供了WebSocket高階編程API,在一般場合下,可以替代socket低階函數(shù)編程。python 提供了更簡潔的編程實現(xiàn)方式。下面展示了實例代碼方式,說明如何開發(fā) Python websocket 服務器代碼,python websocket 客戶端, 以及javascript websocket 代碼。
2. 如何用 Python 搭建 Websocket 服務
python 第3方庫 websockets 提供了websocket 實現(xiàn)框架,支持asyncio, 性能強大,穩(wěn)定性好,可以用于生產環(huán)境。
2.1 安裝websockets包
pip install websockets
2.2 編寫 server 端代碼
Websocket服務端代碼是面向多用戶的長連接,因此本文采用了python3.7 版本的 asyncio 異步方式編寫 websocket server 代碼。
服務端也可使用 ThreadPoolExecutor 線程池方式同時處理多連接的場景,用戶較多時,性能明顯不如asyncio異步方式。
websockets 模塊 server端的主要方法:
- recv() 收消息
- send() 發(fā)送消息
- serve() 創(chuàng)建 server 對象
實現(xiàn)步驟:
- 編寫websocket 異步任務處理函數(shù)handler
- 創(chuàng)建1個websocket server 對象
- 異步運行 server對象
websocket 地址格式:
- ws://主機地址:端口號
- wss://主機地址:端口號, wss表示此連接為https 連接。
下面是具體的代碼 server.py
#!/usr/bin/python3
# 主要功能:創(chuàng)建1個基本的websocket server, 符合asyncio 開發(fā)要求
import asyncio
import websockets
from datetime import datetime
async def handler(websocket):
data = await websocket.recv()
reply = f"Data received as \"{data}\". time: {datetime.now()}"
print(reply)
await websocket.send(reply)
print("Send reply")
async def main():
async with websockets.serve(handler, "localhost", 9999):
await asyncio.Future() # run forever
if __name__ == "__main__":
asyncio.run(main())
服務端handler函數(shù)代碼還有1種寫法,適用性更好。
async def handler(websocket):
async for message in websocket:
reply = f"Data received as \"{message}\". time: {datetime.now()}"
print(reply)
await websocket.send(reply)
Websocket協(xié)議本身有心跳機制、連接檢測機制,服務端無須關心客戶端狀態(tài),一旦有異常,會自動斷開連接。
Websockets提供了交互式測試命令,現(xiàn)在可以快速測試一下服務端是否能正常工作:
(1) 啟動服務器: python server.py
(2) 通過命令行連接服務端,并向發(fā)送hello, world
消息,可以看到,收到了服務器的響應。
D:\workplace\python\projects\websock>python -m websockets ws://localhost:9999
Connected to ws://localhost:9999.
> hello, world
< Data received as "hello, world". time: 2023-04-01 09:24:14.787357
Connection closed: 1000 (OK).
當然實際應用時,應按下面步驟來編寫客戶端代碼。
3. Python websocket 客戶端實現(xiàn)代碼
websockets 客戶端提供的主要方法:
- connect() 建立與服務器的連接
- recv(), send() 收發(fā)消息
- close() 顯式地關閉連接
下面看一下示例 client.py
import asyncio
import websockets
import time
async def ws_client(url):
for i in range(1, 40):
async with websockets.connect(url) as websocket:
await websocket.send("Hello, I am PyPy.")
response = await websocket.recv()
print(response)
time.sleep(1)
asyncio.run(ws_client('ws://localhost:9999'))
4. Javascript websocket 客戶端實現(xiàn)代碼
目前主流的瀏覽器都支持websocket協(xié)議。
Javascript websocket 對象的主要屬性與方法:
請參考菜鳥教程的這篇文章:https://www.runoob.com/html/html5-websocket.html
示例代碼: client.html
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>websocket demo</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/css/bootstrap.min.css">
<script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"> </script>
<script src="https://cdn.staticfile.org/popper.js/1.15.0/umd/popper.min.js"></script>
<script src="https://cdn.staticfile.org/twitter-bootstrap/4.3.1/js/bootstrap.min.js"></script>
<script type="text/javascript">
function WebSocketTest() {
text = document.getElementById("div_text");
if ("WebSocket" in window) {
// 打開一個 web socket
var ws = new WebSocket("ws://localhost:9999/handler");
ws.onopen = function () {
// Web Socket 已連接上,使用 send() 方法發(fā)送數(shù)據(jù)
ws.send("Javscript發(fā)送的數(shù)據(jù)");
text.innerHTML = "數(shù)據(jù)發(fā)送中...";
alert("數(shù)據(jù)發(fā)送中...");
};
ws.onmessage = function (evt) {
var received_msg = evt.data;
text.innerHTML = "收到的數(shù)據(jù):" + received_msg;
alert("數(shù)據(jù)已接收...");
};
ws.onclose = function () {
// 關閉 websocket
text.innerHTML = "連接已關閉...";
alert("連接已關閉...");
};
}
else {
// 瀏覽器不支持 WebSocket
alert("您的瀏覽器不支持 WebSocket!");
}
}
</script>
</head>
<body>
<div class="col-md-6 m-5 p-2" id="div_ws">
<a class="btn btn-primary" href="javascript:WebSocketTest()">連接WebSocket</a>
</div>
<div class="col-md-6 border border-primary mx-5 p-2 " id="div_text" style="margin:20px;height:100px;">
display communicate text
</div>
</body>
</html>
5. 測試websocket
上述3個文件都放在同1個目錄下,打開兩個終端窗口,先運行server.py, 再運行 client,py。
Output結果
在chrome 或edge 中運行client.html, 可以看到websocket 連接建立,發(fā)送,接收,關閉各階段的狀態(tài)。
能夠看到,服務器與客戶端之間的通信是雙向的,而且是長連接,客戶端斷開后,服務器仍然保持偵聽狀態(tài),而且不需要accept操作。websocket發(fā)送、接收文件也不需要 socket 對發(fā)送窗口 buffer 進行控制,因此是 socket 開發(fā)非常好的替代。
注:Python異步websocket服務器最終性能與代碼質量、服務器硬件、網(wǎng)絡等緊密相關,可以使用 Websocket-benchmarker 測試工具來測試服務器。
6. 服務器向客戶端廣播消息
websockets 模塊支持向所有連接的客戶廣播消息,
用1個簡單的例子來演示,實現(xiàn)步驟:
- 保存每個 websocket 客戶連接
- 向每個客戶發(fā)送消息
將前面的server,.py 代碼修改后如下:文章來源:http://www.zghlxwxcb.cn/news/detail-780629.html
#!/usr/bin/python3
# 主要功能:創(chuàng)建1個基本的websocket server, 符合asyncio 開發(fā)要求
import asyncio
import websockets
from datetime import datetime
# Set of connected clients
connected_clients = set()
async def handler(websocket, path):
# Add the client to the connected clients set
connected_clients.add(websocket)
try:
# Keep listening for incoming messages from the client
async for message in websocket:
# Broadcast the message to all connected clients
await broadcast(message)
finally:
connected_clients.remove(websocket)
async def broadcast(message):
# Broadcast the message to all connected clients
for client in connected_clients:
await client.send(message)
async def main():
async with websockets.serve(handler, "localhost", 9998):
await asyncio.Future() # run forever
loop = asyncio.get_running_loop() #獲取當前event_loop對象
loop.create_task(broadcast()) # 添加新的異步廣播任務
if __name__ == "__main__":
asyncio.run(main())
本例中,當服務器收到1條消息時,會廣播給所有用戶。文章來源地址http://www.zghlxwxcb.cn/news/detail-780629.html
到了這里,關于由淺入深介紹 Python Websocket 編程的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!