简介:WebSocket是一种全新的协议,随着HTML5的不断完善,越来越多的现代浏览器开始全面支持WebSocket技术了,它将TCP的Socket(套接字)应用在了webpage上,从而使通信双方建立起一个保持在活动连接通道。
运行流程:浏览器通过Javascript向服务器发起WebSocket连接的请求,在WebSocket连接建立成功后,客户端和服务器就可以通过TCP连接传输数据。因为WebSocket连接本质上是TCP连接,不需要每次传输都带上重复的头部数据,所以它的数据传输量比轮询和Comet(长连接)技术小很多。
原理:WebSocket协议是借用HTTP协议的1.01 switch protocol(服务器根据客户端的指定,将协议转换成为Upgrade首部所列的协议)来达到协议转换的,从HTTP协议切换成WebSocket通信协议。
具体连接方式:通过在请求头中添加upgrade:websocket及通信秘钥(Sec-WebSocket-Key),使双方握手成功,建立全双工通信。
WebSocket客户端连接报文:
WebSocket服务端响应报文:
要对应安装不然会出现版本问题
pipinstallgevent
pipinstalleventlet
pipinstallFlask-SocketIO==4.3.1
pipinstallpython-engineio==3.13.2
pipinstallpython-socketio==4.6.0
fromflaskimportFlask, render_template
fromflask_socketioimportSocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
if__name__ == '__main__':
socketio.run(app)
客户端和服务端使用SocketIO时,消息都被当作事件进行接收。在客户端,Javascript 通过回调函数处理事件;在Flask-SocketIO服务端,每个事件都有对应的事件函数,类似原生Flask中,路由都有对应的视图函数。
前端代码:
Test
Hello World!
后端代码:
fromflaskimportFlask, render_template
fromflask_socketioimportSocketIO, emit
fromthreadingimportThread
importtime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
#socketio = SocketIO()
socketio = SocketIO(logger=True, engineio_logger=True) # 将日志输出到终端
socketio.init_app(app, cors_allowed_origins='*') # 解决跨域问题
name_space = '/dcenter' # 命名空间必须与前端一致
# 初始化连接函数
@socketio.on('connect', namespace=name_space)
defconnected_msg():
print('client connected.')
# 初始化断开函数
@socketio.on('disconnect', namespace=name_space)
defdisconnect_msg():
print('client disconnected.')
# 路由路径
@app.route('/')
defindex():
returnrender_template('index.html')
# 接收客户端消息(事件命名必须与前端一致)
@socketio.on('my event') # 匿名事件
defhandle_my_custom_event(json):
print('received json: '+str(json))
# 接收客户端消息(事件命名必须与前端一致)
@socketio.on('my event', namespace=name_space) # 绑定命名空间
defhandle_my_custom_event(json):
print('received json: '+str(json))
if__name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
服务器响应数据:
前端代码:
Test
{# #}
Hello World!
发送
后端代码:
fromflaskimportFlask, render_template
fromflask_socketioimportSocketIO, emit
fromthreadingimportThread
importmultiprocessing
importtime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
# socketio = SocketIO()
socketio = SocketIO(logger=True, engineio_logger=True) # 将日志输出到终端
socketio.init_app(app, cors_allowed_origins='*') # 解决跨域问题
name_space = '/dcenter'
# 初始化断开函数
@socketio.on('connect', namespace=name_space)
defconnected_msg():
print('client connected.')
# 初始化断开连接函数
@socketio.on('disconnect', namespace=name_space)
defdisconnect_msg():
print('client disconnected.')
@app.route('/')
defindex():
returnrender_template('index.html')
# 匿名函数
@socketio.on('message')
defhandle_message(message):
print('received message: '+message)
if__name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
先访问,点击发送:
服务器实时接收结果:
在启用广播选项的情况下发送消息时,连接到命名空间的所有客户端都会收到它,包括发送者。当不使用命名空间时,连接到全局命名空间的客户端会收到消息。请注意,不会为广播消息调用回调。
设置参数broadcast=True就会启动广播功能。
前端代码:
Test
Hello World!
后端代码:
fromflaskimportFlask, render_template
fromflask_socketioimportSocketIO, emit
fromthreadingimportThread
importtime
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'
#socketio = SocketIO()
socketio = SocketIO(logger=True, engineio_logger=True) # 将日志输出到终端
socketio.init_app(app, cors_allowed_origins='*') # 解决跨域问题
name_space = '/dcenter' # 命名空间
# 初始化连接函数
@socketio.on('connect', namespace=name_space)
defconnected_msg():
print('client connected.')
# 回调函数
defcb():
print('vvv==bbb')
# 初始化断开连接函数
@socketio.on('disconnect', namespace=name_space)
defdisconnect_msg():
print('client disconnected.')
# 测试函数向客户端发送数据
deftest():
event_name = 'dcenter'
foriinrange(11):
broadcasted_data = {'data': f"test message!,{i}"}
# 消息广播
socketio.emit(event_name, broadcasted_data, broadcast=False, namespace=name_space, callback=cb())
time.sleep(1)
@app.route('/')
defindex():
# 开启多线程进行消息发送
td = Thread(target=test)
td.start()
returnrender_template('index.html')
if__name__ == '__main__':
socketio.run(app, host='0.0.0.0', port=5000, debug=True)
访问http://127.0.0.1:5000/后,服务器会在11秒内依次向客户端发送数据
1、命名空间和事件命名必须前后端保持一致。
2、服务器的信息发送与广播只支持线程广播但不支持进程广播。
实际应用场景中,可能需要给用户分组。比如,聊天室,不同用户只能收到他们所在房间的消息。通过join_room() 和 leave_room() 可以实现上述功能:
from flask_socketio import join_room, leave_room
@socketio.on('join')
def on_join(data):
username = data['username']
room = data['room']
join_room(room)
send(username + ' has entered the room.', room=room)
@socketio.on('leave')
def on_leave(data):
username = data['username']
room = data['room']
leave_room(room)
send(username + ' has left the room.', room=room)
send() 和emit() 函数接受room 参数。
所有客户端连接时,会被分配一个房间。默认房间名称为连接的session ID,Flask中通过request.sid获取该ID。客户端能加入所有存在的房间。客户端断开时,所有它加入的房间都会移除它。上下文外的socketio.send() 和 socketio.emit()也可以接收room参数,来给房间中所有客户端广播。
因为所有客户端在加入时,都被指定了一个私人的房间,所以,如果想要发送消息给指定客户端,也可以通过指定消息的room参数为该客户端session ID来实现。
参考文献:https://zhuanlan.zhihu.com/p/376370796
https://blog.csdn.net/weixin_46020624/article/details/123619230?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522167513282616800225584650%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=167513282616800225584650&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-123619230-null-null.142^v71^wechat,201^v4^add_ask&utm_term=Flask-SocketIO&spm=1018.2226.3001.4187