flask_socketio收发消息
此过程结合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