Flask WebSocket学习笔记

9 篇文章 0 订阅

WebSocket

简介: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服务端响应报文

1. Flask-SocketIO

要对应安装不然会出现版本问题

pipinstallgevent
pipinstalleventlet
pipinstallFlask-SocketIO==4.3.1
pipinstallpython-engineio==3.13.2
pipinstallpython-socketio==4.6.0

1.1 初始化

fromflaskimportFlask, render_template
fromflask_socketioimportSocketIO
​
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
​
if__name__ == '__main__':
    socketio.run(app)

1.2 客户端向服务器发送数据

客户端和服务端使用SocketIO时,消息都被当作事件进行接收。在客户端,Javascript 通过回调函数处理事件;在Flask-SocketIO服务端,每个事件都有对应的事件函数,类似原生Flask中,路由都有对应的视图函数。

前端代码:

<!DOCTYPE html>
<htmllang="en">
<head>
    <metacharset="UTF-8">
    <title>Test</title>
    <scripttype="text/javascript"src="{{ url_for('static', filename = 'js/jquery.min.js') }}"></script>
    <scripttype="text/javascript"src="{{ url_for('static', filename = 'js/socket.io.min.js') }}"></script>
</head>
<body>
    <h1>Hello World!</h1>
    <pid="t"></p>
    <script>
        varname_space='/dcenter'// 这里的命名空间必须与后端代码的命名空间一样,要不然无法建立连接
        varsocket=io.connect(location.protocol+'//'+document.domain+':'+location.port+name_space)  //初始化建立连接
        {# 向服务器发送信息 #}
        socket.on('connect',function () {
            // 这里的事件命名必须与后端绑定的事件命名一样,不然接收不到消息
            socket.emit('my event', {data: 'I\'m connected!'})
        })
    </script>
</body>
</html>

后端代码:

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)​

服务器响应数据:

1.3 服务器实时接收数据

前端代码:

<!DOCTYPE html>
<htmllang="en">
<head>
    <metacharset="UTF-8">
    <title>Test</title>
    <scripttype="text/javascript"src="{{ url_for('static', filename = 'js/jquery.min.js') }}"></script>
    <scripttype="text/javascript"src="{{ url_for('static', filename = 'js/socket.io.min.js') }}"></script>
{#    <scripttype="text/javascript"src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.6/socket.io.min.js"></script>#}
</head>
<body>
    <h1>Hello World!</h1>
    <pid="t"></p>
    <ahref="javascript:a()">发送</a>
    <script>
        // 向服务器发送信息,如果要服务器实时接收数据的话,需建立连接与发送数据要同步进行(例如封装在一个函数里)
        functiona(){
            varsocket=io.connect('http://'+document.domain+':'+location.port);
            socket.on('connect', function() {
                socket.emit('message', 'I\'m connected!');
                {#socket.emit('message', {data: 'I\'m connected!'});#}
                socket.close() // 这里很关键,如果发送完数据没有关闭通道的话,客户端就会一直出现轮询现象
            });
            console.log('kkk')
        }
    </script>
</body>
</html>

后端代码:

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)

先访问,点击发送:

服务器实时接收结果:

1.4 服务器向客户端发送数据

在启用广播选项的情况下发送消息时,连接到命名空间的所有客户端都会收到它,包括发送者。当不使用命名空间时,连接到全局命名空间的客户端会收到消息。请注意,不会为广播消息调用回调。

设置参数broadcast=True就会启动广播功能。

前端代码:

<!DOCTYPE html>
<htmllang="en">
<head>
    <metacharset="UTF-8">
    <title>Test</title>
    <scripttype="text/javascript"src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <scripttype="text/javascript"src="//cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
</head>
<body>
    <h1>Hello World!</h1>
    <pid="t"></p>
    <script>
        varname_space='/dcenter'// 这里的命名空间必须与后端代码的命名空间一样,要不然无法建立连接
        varsocket=io.connect(location.protocol+'//'+document.domain+':'+location.port+name_space)
         // 这里的事件命名必须与后端绑定的事件命名一样,不然接收不到消息
        socket.on('dcenter', function (res) {
            console.log(res)
            lett=res.data
            if (t) {
                $("#t").append(t).append('<br/>');
            }
        })
    </script>
</body>
</html>

后端代码:

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.5 总结

1、命名空间事件命名必须前后端保持一致。

2、服务器的信息发送广播只支持线程广播但不支持进程广播。

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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

冒险岛_0_

您的打赏是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值