基于 SocketIO 消息協(xié)議設(shè)計報文規(guī)范,構(gòu)建FastAPI上的SocketIO 應(yīng)用
最近在研究Python下整合FastAPI的Socket.IO 應(yīng)用,對于其WebSocket的消息報文協(xié)議進(jìn)行了深入了解,并整理了相關(guān)的協(xié)議內(nèi)容,整合到FastAPI的WebSocket通訊處理中,用作多端的消息通訊,如聊天,系統(tǒng)信息通知等。
1. 總體設(shè)計
- 統(tǒng)一事件名:客戶端和服務(wù)端只監(jiān)聽/發(fā)送一個事件,例如:"message"。
- 消息類型區(qū)分:通過 msgtype 字段區(qū)分不同的業(yè)務(wù)邏輯。
- 通用字段:每條消息都必須包含的基礎(chǔ)字段(如 msgtype, sender, timestamp)。
- 擴(kuò)展字段:放在 payload 里,根據(jù)不同類型自由定義。
- 報文格式:統(tǒng)一為 JSON 格式。
- 廣播機(jī)制:服務(wù)端可以廣播消息到所有客戶端,方便消息的實時推送。
- 房間機(jī)制:服務(wù)端可以創(chuàng)建、加入或離開房間,方便消息的分發(fā)。
2. 消息格式
{
"msgtype": "string", // 消息類型,如 "chat", "notification", "command"
"sender": "string", // 發(fā)送者標(biāo)識,如用戶ID或系統(tǒng)標(biāo)識
"timestamp": "number", // 消息發(fā)送時間戳(毫秒)
"payload": { // 消息內(nèi)容(不同 msgtype 有不同格式)
// 根據(jù) msgtype 定義不同的結(jié)構(gòu)
},
"room": "string", // 房間標(biāo)識(可選),表示消息所屬的房間
"touser": "string", // 接收者標(biāo)識(可選),表示消息的目標(biāo)用戶,多個接收者用‘|’分隔
"totag": "string", // 接收者標(biāo)簽(可選),表示消息的目標(biāo)用戶標(biāo)簽,多個標(biāo)簽用‘|’分隔
}
例子:
{
"msgtype": "chat",
"room": "room1",
"sender": "user1",
"timestamp": 1612345678901,
"payload": {
"text": "Hello, world!"
}
}
3. 消息類型
3.1 系統(tǒng)類消息(system)
系統(tǒng)類消息由系統(tǒng)發(fā)送,用于通知客戶端或服務(wù)端狀態(tài)變化。
{
"msgtype": "system_notice",
"sender": "system",
"timestamp": 1694412000000,
"payload": {
"level": "info", // info, warning, error
"text": "服務(wù)器即將維護(hù)"
}
}
3.2 聊天類消息(chat)
聊天類消息由用戶發(fā)送,用于聊天。
{
"msgtype": "chat_message",
"room": null,
"sender": "alice",
"timestamp": 1694412000000,
"payload": {
"text": "你好,大家!",
"extra": {
"emoji": "??"
}
}
}
3.3 房間操作類消息(room)
房間操作類消息用于創(chuàng)建、加入或離開房間。
{
"msgtype": "room_event",
"room": "room1",
"sender": "bob",
"timestamp": 1694412000000,
"payload": {
"action": "join", // join, leave, create, destroy
"user": "bob"
}
}
3.4 ACK / 狀態(tài)反饋消息(ack)
ACK / 狀態(tài)反饋消息用于確認(rèn)客戶端或服務(wù)端收到消息。
{
"msgtype": "ack",
"sender": "server",
"timestamp": 1694412000000,
"payload": {
"status": "ok", // ok, fail
"requestId": "abc123",
"msg": "消息已送達(dá)"
}
}
3.5 命令類消息(command)
命令類消息用于向服務(wù)端發(fā)送指令。
{
"msgtype": "command",
"sender": "user1",
"timestamp": 1694412000000,
"payload": {
"command": "create_room",
"args": {
"name": "room1"
}
}
}
3.6 圖片類消息(image)
圖片類消息用于發(fā)送圖片。
{
"msgtype": "image",
"room": "room1",
"sender": "user1",
"timestamp": 1694412000000,
"payload": {
"url": "https://example.com/image.jpg"
}
}
3.7 文件類消息(file)
文件類消息用于發(fā)送文件。
{
"msgtype": "file",
"room": "room1",
"sender": "user1",
"timestamp": 1694412000000,
"payload": {
"url": "https://example.com/file.pdf"
}
}
3.8 語音類消息(voice)
3.9 視頻類消息(video)
3.10 位置類消息(location)
3.11 其他類型消息(other)
3.12 自定義消息(custom)
4、Python 服務(wù)端示例
import time
import socketio
sio = socketio.AsyncServer(async_mode="asgi", cors_allowed_origins="*")
def make_message(msg_type, sender, payload, room=None):
return {
"msgtype": msg_type,
"room": room,
"sender": sender,
"timestamp": int(time.time() * 1000),
"payload": payload
}
@sio.event
async def chat_message(sid, data):
msg = make_message("chat_message", sid, {"text": data})
await sio.emit("message", msg) # 廣播
@sio.event
async def join_room(sid, room):
sio.enter_room(sid, room)
msg = make_message("room_event", "system", {"action": "join", "user": sid}, room)
await sio.emit("message", msg, room=room)
5、Vue3 前端示例
socket.on("message", (msg) => {
switch (msg.msgtype) {
case "chat_message":
console.log(`[${msg.sender}] 說: ${msg.payload.text}`);
break;
case "system_notice":
console.log(`系統(tǒng)通知: ${msg.payload.text}`);
break;
case "room_event":
console.log(`房間 ${msg.room} 事件: ${msg.payload.action} -> ${msg.payload.user}`);
break;
case "ack":
console.log(`ACK: ${msg.payload.status} (${msg.payload.msg})`);
break;
default:
console.warn("未知消息類型:", msg);
}
});
6、通信流程示例
6.1 普通消息
- 客戶端發(fā)送:
{
"msgtype": "chat_message",
"sender": "alice",
"timestamp": 1694412000000,
"payload": {
"text": "Hello World"
}
}
- 服務(wù)端廣播:
{
"msgtype": "chat_message",
"sender": "alice",
"timestamp": 1694412000500,
"payload": {
"text": "Hello World"
}
}
6.2 房間消息
6.2.1 創(chuàng)建房間
- 客戶端發(fā)送:
{
"msgtype": "command",
"sender": "bob",
"timestamp": 1694412000000,
"payload": {
"command": "create_room",
"args": {
"name": "room1"
}
}
}
2. 服務(wù)端響應(yīng):
```json
{
"msgtype": "ack",
"sender": "server",
"timestamp": 1694412000000,
"payload": {
"status": "ok",
"requestId": "abc123",
"msg": "房間 room1 創(chuàng)建成功"
}
}
6.2.2 加入房間
1.客戶端發(fā)送:
{
"msgtype": "room_event",
"sender": "alice",
"timestamp": 1694412000000,
"payload": {
"action": "join",
"user": "alice"
}
}
2.服務(wù)端響應(yīng):
{
"msgtype": "ack",
"sender": "server",
"timestamp": 1694412000000,
"payload": {
"status": "ok",
"requestId": "abc123",
"msg": "用戶 alice 加入房間 room1"
}
}
3.服務(wù)端廣播:
{
"msgtype": "room_event",
"room": "room1",
"sender": "server",
"timestamp": 1694412000000,
"payload": {
"action": "join",
"user": "alice"
}
}
6.2.3 離開房間
1.客戶端發(fā)送:
{
"msgtype": "room_event",
"sender": "alice",
"timestamp": 1694412000000,
"payload": {
"action": "leave",
"user": "alice"
}
}
2.服務(wù)端響應(yīng):
{
"msgtype": "ack",
"sender": "server",
"timestamp": 1694412000000,
"payload": {
"status": "ok",
"requestId": "abc123",
"msg": "用戶 alice 離開房間 room1"
}
}
7、優(yōu)點
-
統(tǒng)一事件名:客戶端和服務(wù)端只監(jiān)聽/發(fā)送一個事件,降低耦合度,提高代碼可讀性。
-
消息類型區(qū)分:通過 msgtype 字段區(qū)分不同的業(yè)務(wù)邏輯,提高消息處理效率。
-
通用字段:每條消息都必須包含的基礎(chǔ)字段(如 msgtype, sender, timestamp),方便服務(wù)端處理。
-
擴(kuò)展字段:放在 payload 里,根據(jù)不同類型自由定義,方便消息內(nèi)容的擴(kuò)展。
-
狀態(tài)反饋消息:ACK / 狀態(tài)反饋消息用于確認(rèn)客戶端或服務(wù)端收到消息,方便客戶端處理。
-
命令類消息:命令類消息用于向服務(wù)端發(fā)送指令,方便服務(wù)端處理。
-
廣播機(jī)制:服務(wù)端可以廣播消息到所有客戶端,方便消息的實時推送。
-
房間機(jī)制:服務(wù)端可以創(chuàng)建、加入或離開房間,方便消息的分發(fā)。
-
擴(kuò)展性:服務(wù)端和客戶端可以自由擴(kuò)展消息類型,增加更多的業(yè)務(wù)邏輯。
-
兼容性:服務(wù)端和客戶端可以兼容 SocketIO 協(xié)議,方便與其他服務(wù)端和客戶端通信。
-
易用性:服務(wù)端和客戶端可以直接使用 SocketIO 庫,降低學(xué)習(xí)成本。
-
性能:服務(wù)端和客戶端可以利用 WebSocket 協(xié)議,提高消息傳輸效率。
8、關(guān)于 Socket.IO的掛載的注意事項
Socket.IO 是一個基于 WebSocket 的實時通信協(xié)議,它可以實現(xiàn)瀏覽器和服務(wù)器之間的雙向通信。
Socket.IO 協(xié)議的實現(xiàn)主要依賴于兩個部分:
- Socket.IO 服務(wù)器:Socket.IO 服務(wù)器負(fù)責(zé)維護(hù)連接,處理消息,并向客戶端推送消息。
- Socket.IO 客戶端:Socket.IO 客戶端負(fù)責(zé)維護(hù)連接,向 Socket.IO 服務(wù)器發(fā)送消息,并接收消息。
如果你用 app = register_app(),然后用 uvicorn main:app 或者 config = uvicorn.Config(app, ...),這樣啟動的“app”是原生 FastAPI 實例,不包含 Socket.IO 的功能,所以前端 Socket.IO 客戶端連接時不會觸發(fā) @sio.event 的回調(diào)!
解決方法:
你需要讓 Uvicorn 啟動的是 ASGI 的組合應(yīng)用,即 sio_app,而不是純 FastAPI 的 app。
import socketio
from fastapi import FastAPI
def register_app():
app = FastAPI()
# ...注冊路由等...
return app
app = register_app()
sio = socketio.AsyncServer(async_mode='asgi', cors_allowed_origins='*')
sio_app = socketio.ASGIApp(sio, other_asgi_app=app)
# main.py 入口
if __name__ == "__main__":
import uvicorn
# 下面兩種寫法都可以
uvicorn.run(sio_app, host="0.0.0.0", port=8000)
# 或
# config = uvicorn.Config(sio_app, host="0.0.0.0", port=8000)
# server = uvicorn.Server(config)
# server.run()
注意:一定要啟動的是 sio_app,不是 app!
你可以繼續(xù)用 register_app() 組織你的 FastAPI 配置,但最終給 Uvicorn 的必須是 sio_app。
最后我們來看看再FastAPI項目上對接的例子通訊截圖(尚未完工,持續(xù)改進(jìn)優(yōu)化中)。
服務(wù)端的信息截圖

客戶端信息截圖

專注于代碼生成工具、.Net/Python 框架架構(gòu)及軟件開發(fā),以及各種Vue.js的前端技術(shù)應(yīng)用。著有Winform開發(fā)框架/混合式開發(fā)框架、微信開發(fā)框架、Bootstrap開發(fā)框架、ABP開發(fā)框架、SqlSugar開發(fā)框架、Python開發(fā)框架等框架產(chǎn)品。
??轉(zhuǎn)載請注明出處:撰寫人:伍華聰??http://www.iqidi.com?
????
浙公網(wǎng)安備 33010602011771號