此過程結合flask-socketio 和 sockeio.js 講解
1.初始 flask-socketio
https://blog.csdn.net/eleanoryss/article/details/109600154
這個鏈接是介紹 websocket 和 HTTP 長連接的差別
websocket 說白一點就是,建立客戶端和服務端雙向通訊通道, 服務器可以主動向客戶端發消息。
https://www.jianshu.com/p/d81397edd2b1
上邊這個鏈接是一個對官方文檔的翻譯。
2.安裝
python 3.9
socket.io.js 3.1.3
https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js
pip install flask-socketio
依賴安裝
pip install eventlet
3.初始化項目
from flask_socketio import SocketIO
import random
async_mode = None
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
if __name__ == '__main__':
socketio.run(app)
# 或者 set flask_app=app app指的是項目
# flask run
# 兩種方式都可以
# socketio = SocketIO()
# socketio.init_app(app)
4. 客戶端js
相關文檔
https://socket.io/get-started/chat#Integrating-Socket-IO
cdn 資源
https://cdnjs.com/libraries/socket.io
4.接收消息和發送消息
客戶端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js" integrity="sha512-2RDFHqfLZW8IhPRvQYmK9bTLfj/hddxGXQAred2wNZGkrKQkLGj8RCkXfRJPHlDerdHHIzTFaahq4s/P4V6Qig==" crossorigin="anonymous"></script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function() {
name_space = "/test";
var socket = io.connect("http://127.0.0.1:5000");
// 連接成功后發送消息
socket.on("connect", function(){
// 發送普通消息
socket.send("connect once");
})
// 接收后台發送至ceishi的消息
socket.on("ceshi", function(data){
console.log("有名" + data)
})
// 接收后台消息
socket.on("message", function(data){
console.log("無名"+data)
})
});
</script>
</body>
</html>
后台
@socketio.on("message")
def message(msg):
print("message", msg)
socketio.send(msg, broadcast=True)
socketio.emit('ceshi', "測試")
# 接受 前端向json 接口發送的數據 對應前端 --> socket.emit("json", {"hello": "world"})
@socketio.on("json")
def json_msg(msg):
print("json", msg)
socketio.send(msg, broadcast=True)
講解:
之前看文檔不太明白,測試了好久.
socketio 發送消息分為 send 和 emit
send(): 發送至未命名的一般默認為message, 一會來講解message是什么
emit(): 發送到指定接受的活動上.
socketio.on
你可以認為這個是socketio 接收函數的接口, 用在客戶端<前端>就相當於 websocket 的 ws.onmessage 接受后台傳過來的數據, 用在后台同樣
socketio.emit("活動名", 消息, namespace<命名空間>)
在上邊代碼有一句:
socketio.emit('ceshi', "測試") # 這句話的意思是, 發送至活動ceshi 一條消息為 "測試"
在客戶端接收的地方為:
socket.on("ceshi", function(data){
console.log("有名" + data)
}) # 接受后台發送到 ceshi 活動的信息
后台同樣:
@socketio.on("message") # 接受發送到 message 活動的消息
@socketio.on("json") # 接受發送到 json 活動的消息
@socketio.on("connect") # 當客戶端連接的時候觸發
@socketio.on("disconnect") # 當客戶端斷開連接的時候觸發
# 除外你還可以自定義名字
socketio.send
不指定活動名發送, 一般默認發送到 message 中
namespace: 下邊講解
客戶端代碼:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/3.1.3/socket.io.js" integrity="sha512-2RDFHqfLZW8IhPRvQYmK9bTLfj/hddxGXQAred2wNZGkrKQkLGj8RCkXfRJPHlDerdHHIzTFaahq4s/P4V6Qig==" crossorigin="anonymous"></script>
</head>
<body>
<script type="text/javascript">
$(document).ready(function() {
name_space = "/test";
// 連接后台
var socket = io.connect("http://127.0.0.1:5000");
// 連接成功后發送消息
socket.on("connect", function(){
// 發送普通消息
socket.send("connect once");
// 發送json數據
socket.emit("json", {"hello": "world"})
})
socket.on("ceshi", function(data){
console.log("有名" + data)
})
// 接受后台消息
socket.on("message", function(data){
console.log("無名"+data)
})
});
</script>
</body>
</html>
后台代碼:
#encoding:utf-8
#!/usr/bin/env python
from flask import Flask, render_template
from flask_socketio import SocketIO
import random
async_mode = None
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
@app.route('/')
def index():
return render_template('socketio.html')
# 默認接受send 發送過來的, 同時也可以接受 emit("message", "測試")
@socketio.on("message")
def message(msg):
print("message", msg)
socketio.send(msg, broadcast=True)
socketio.emit('ceshi', "測試")
# 接受 前端向json 接口發送的數據 對應前端 --> socket.emit("json", {"hello": "world"})
@socketio.on("json")
def json_msg(msg):
print("json", msg)
socketio.send(msg, broadcast=True)
if __name__ == '__main__':
socketio.run(app, debug=True)
*** 至此你可以通過將接受的數據添加到頁面中的某個div,簡單實現一個在線聊天
broadcast = True
這是 send 和 emit 的參數,指廣播, 加上這個之后所有在指定命名空間中的客戶端都會收到消息
5. namespace
namespace
命名空間: 相當於 路由的name
用來區分邏輯的,
var socket = io.connect("http://127.0.0.1:5000"+"/ceshi"); # 指定連接 ceshi 命名空間
后台使用:
@socketio.on("ceshi", namespace="/ceshi")
socketio.emit("ceshi", "message", namespace="/cesi")
1.也可以在發送消息中使用指明發送的命名空間, 加上后只會發送到連接到指定命名空間
2.如果連接指明了命名空間, 那么發送的時候不知明,則連接到指定空間的,不會接受消息
6.room 房間
在客戶端方面房間的概念, 用來對用戶分組,以便在某個命名空間下進一步的通信頻道分離. 將客戶端加入/遷出房間的操作在服務器端實現, join_room() 和 leave_room() , 還可以使用 close_room() 來刪除房間, rooms () 函數返回房間客戶端列表.
為了保持簡單,CatChat中並沒有添加房間功能,我們這里僅介紹實現的基本方法。首先,你需要創建一個Room模型存儲房間數據。房間可以使用任意的字符串或數字作為標識,所以可以使用主鍵列作為標
識,另外再創建一個name列用於存儲房間的顯示名稱。同時,我們還要在程序中提供房間的創建、編輯和刪除操作。在房間聊天頁面,我們可以在客戶端的connect事件監聽函數中使用emit()函數觸發服務器端自定義的join事件;同樣,用戶單擊離開按鈕離開房間后在客戶端disconnect事件處理函數中使用emit()函數觸發服務器端定義的leave事件:
socket.on('connect', function() {
socket.emit('join');
});
socket.on('disconnect', function() {
socket.emit('leave');
});
JavaScript
Copy
在服務器端,自定義的join和leave事件分別用來將用戶加入和移出
房間,這兩個自定義事件的處理函數如下所示:
from flask_socketio import join_room, leave_room
@socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
# 發送至客戶端 status 接口, 只發送給在room房間的用戶
emit('status', username + ' has entered the room.', room=room) # 只有指定房間內的用戶會收到消息, 這個room 名可以是字符串等唯一標識符
@socketio.on('leave')
def on_leave(data):
username = data['username']
room = data['room']
leave_room(room)
emit('status', username + ' has left the room.', room=room)
Python
-
經測試發現,我在左邊窗口點擊加入, 左邊窗口會收到房間里發送的消息,右邊不會。
-
再次點擊右邊的,哪左邊右邊都會收到消息,因為兩個都在房間里了。
7.sid 用戶連接唯一標識
可以指定sid 發送消息, 但是sid 每次連接都會變,所以可以跟用戶表綁定, 每次連接更新用戶表sid, 在本次連接用到的時候取出用即可。
擴展:
經上述測試后我們是否可以簡單實現房間聊天功能。
1. 用戶去創建自己的聊天室並命名(存到數據庫中, 可以有唯一id + 聊天室名字),
2. 展示所有聊天室(從數據庫中獲取)
3. 用戶選擇聊天室(向后台join接口發送用戶信息, 同時發送房間唯一標識),
4. 后台將用戶加入聊天室,使用(join_room) 即可
5. 用戶發送消息的時候,出消息內容還要加上聊天室唯一標識, 后台去向指定標識傳就可以
6. 用戶關閉聊天室,采用 close_room(room) 關閉
再次擴展:
1.聊天室信息存到 redis 中, 在redis中存儲聊天室在線人數,每有人連接進來更新一下。
2. 單對單聊天擴展
這種情況,要考慮消息離線問題,需要數據庫/ 消息隊列。
簡單點采用數據庫:
單對單原理:
相當於創建1對1的房間
1. 與誰聊天就和誰創建一個房間,數據庫為房間表 + 用戶表, 一 對 多, 采用join_room()
1>每次打開聊天界面,把后台消息刷新到頁面上。
2>刪除聊天的時候刪除房間
2. 同樣來一個 房間表 + 用戶表, 一 對 多, 采用sid 指定 用戶的sid 發送消息,
Copy
在這兩個事件處理器中,我們分別調用Flask-SocketIO提供的
join_room()和leave_room()函數,並傳入房間的唯一標識符。
提示
房間也支持命名空間,通過join_room()和leave_room()函數的
namespace參數指定,默認使用當前正在處理的命名空間,可以通過
Flask-SocketIO附加在請求對象上的namespace屬性獲得,即
request.namesapce。
同樣,在發送事件時,也要指定發到哪個房間,這通過使用
send()和emit()函數中的room參數來指定。比如,下面是創建廣播
新消息的room message事件處理函數:
@socketio.on('room message')
def new_room_message(message_body):
emit('message', {'message': current_user.username + ':' + message_body}, room=current_user.room)
如果你僅需要對用戶進行分組,那么房間是你的最佳選擇。命名空
間是在程序層面上的頻道分離。如果我們要在程序中同時實現全局聊
天、匿名聊天室、房間、私聊,這四類功能對消息的處理各自不同,所
以我們需要為這四類功能指定不同的命名空間(全局聊天可以使用默認
的全局命名空間)。在需要分離通信頻道時,我們需要根據程序的特點
來決定方式:僅使用命名空間、僅使用房間或兩者結合使用。
附注
你可以通過Flask-SocketIO作者Miguel Grinberg提供的這個聊天程序
(https://github.com/miguelgrinberg/Flask-SocketIO-Chat)示例了解關於
房間的具體實現。
順便說一下,基於房間你也可以實現私信/私聊功能。只需要把
room設為代表某個用戶的唯一值,在發送事件時,就只有目標用戶的客
戶端才能接收到事件。你可以把這種實現方法理解為“一個人的房間”。
這個能代表用戶的唯一值可以是主鍵值、username或是Flask-SocketIO附
加到request對象上代表每個客戶端id的session id(request.sid)。
提示
如果你使用request.sid作為唯一值,那么需要在User模型中添加一個
sid字段存儲這個值,然后在服務器端的connect事件處理函數中更新這
個值。
部分概念和內容來自:《Flask Web開發實戰(李輝)》
https://huyu.info/blog/detail/103