欢迎访问我的网站,希望内容对您有用,感兴趣的可以加入我们的社群。

Flask教程(十九)SocketIO

Flask Web 迷途小书童 4年前 (2020-10-08) 10493次浏览 4个评论

软硬件环境

  • windows 10 64bit
  • anaconda3 with python 3.7
  • pycharm 2020.1.2
  • flask 1.1.2
  • flask-socketio 4.3.1

视频看这里

此处是 youtube 的播放链接,需要科学上网。喜欢我的视频,请记得订阅我的频道,打开旁边的小铃铛,点赞并分享,感谢您的支持。

什么是websocket

WebSocket 是一种网络通讯协议,与 HTTP 不同的是,WebSocket 提供全双工通信。也就是说,传统的方式,只有当客户端发起请求后,服务器端才会发送数据,而 WebSocket 可以让服务器主动发送数据给客户端,它是服务器推送技术的一种。

下图是 WebSocketHTTP 的区别

websocket

websocketd

websocketd 是一款非常不错的 WebSocket 服务器,我们首先利用它来了解下标准 WebSocket 的交互方式。先来下载 windows 版本的 websocketd,解压之后进入目录

下面开始编写测试脚本,使用 python 语言,文件名是 test.py,脚本的作用是周期性的向标准输出 stdout 打印一个数值

from sys import stdout
from time import sleep

for count in range(0, 10):
  print(count + 1)
  stdout.flush()
  sleep(0.5)

开启服务

websocketd --port 8080 python test.py

现在需要一个 WebSocket 客户端,我们来到 chrome 商店,安装插件 https://chrome.google.com/webstore/detail/websocket-test-client/fgponpodhbmadfljofbimhhlengambbn , 然后打开插件,输入 WebSocketurl 就可以看到服务器端的输出了

websocketd

websocketd

websocketd

flask-socketio

Flask-SocketIO 使 Flask 应用程序能够访问客户端和服务器之间的低延迟双向通信。客户端应用程序可以使用 JavascriptC++JavaSwift 中的任何 SocketIO 官方客户端库或任何兼容的客户端来建立与服务器的永久连接。

异步模式

flask-socketio 需要底层异步服务的支持,比较灵活的是,它会自己检测当前环境中存在的异步服务,且使用顺序为 eventlet –> gevent –> werkzeug

  • eventlet 性能最佳,支持长轮询和WebSocket传输
  • gevent 在许多不同的配置中得到支持。gevent完全支持长轮询传输,但与eventlet不同,gevent没有本机WebSocket支持。要添加对WebSocket的支持,目前有两种选择:安装gevent-websocket包为gevent增加WebSocket支持,或者可以使用带有WebSocket功能的uWSGI Web服务器。gevent的使用也是一种高性能选项,但略低于eventlet
  • werkzeug 也可以使用基于werkzeugFlask开发服务器,但需要注意的是,它缺乏其他两个选项的性能,因此只应用于简单的开发环境,而且它也仅支持长轮询传输

安装

直接使用 pip 来安装

pip install eventlet
pip install flask-socketio

示例代码

先看服务端代码

文件 run.py

from flask import Flask, render_template
from flask_socketio import SocketIO, emit

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret_key'

socketio = SocketIO()
socketio.init_app(app, cors_allowed_origins='*')

name_space = '/dcenter'

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/push')
def push_once():
    event_name = 'dcenter'
    broadcasted_data = {'data': "test message!"}
    socketio.emit(event_name, broadcasted_data, broadcast=False, namespace=name_space)
    return 'done!'

@socketio.on('connect', namespace=name_space)
def connected_msg():
    print('client connected.')

@socketio.on('disconnect', namespace=name_space)
def disconnect_msg():
    print('client disconnected.')

@socketio.on('my_event', namespace=name_space)
def mtest_message(message):
    print(message)
    emit('my_response',
         {'data': message['data'], 'count': 1})

if __name__ == '__main__':

    socketio.run(app, host='0.0.0.0', port=5000, debug=True)

代码中,我们设定了命名空间 namespace,因为客户端在进行连接的时候需要用到的 url 就包含 namespace,另外还有个概念,就是 eventconnectdisconnect 还有自定义的 my_event 都是

文件 index.html 的代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>SocketIO Demo</title>
    <script type="text/javascript" src="//cdn.bootcss.com/jquery/3.1.1/jquery.min.js"></script>
    <script type="text/javascript" src="//cdn.bootcss.com/socket.io/1.5.1/socket.io.min.js"></script>
</head>
<body>

<h2>Demo of SocketIO</h2>
<div id="t"></div>
<script>
$(document).ready(function () {
    namespace = '/dcenter';
    var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    socket.on('dcenter', function (res) {
        var t = res.data;
        if (t) {
            $("#t").append(t).append('<br/>');
        }

    });
});
</script>
</body>
</html>

启动服务后,我们访问 http://127.0.0.1:5000, 并保持这个页面不动,因为,后面接收到的消息会显示在这个页面上

flask-socketio

这时候,使用浏览器访问 http://127.0.0.1:5000/push

flask-socketio

我们使用这种方式来触发服务器发送消息给客户端,再次回到之前的页面,可以看到已经把服务器端发送过来的消息显示在了页面上

flask-socketio

注意到,上面 javascript 中,url 中使用的协议是 http,而不是 ws,这一点跟原生的 WebSocket 有点区别。

cors跨域错误

如果是前后端分离的系统中,会出现如下的错误

Traceback (most recent call last):
  File "src\\gevent\\greenlet.py", line 854, in gevent._gevent_cgreenlet.Greenlet.run
  File "C:\ProgramData\Anaconda3\envs\FlaskTutorial\lib\site-packages\gevent\baseserver.py", line 34, in _handle_and_close_when_done
    return handle(*args_tuple)
  File "C:\ProgramData\Anaconda3\envs\FlaskTutorial\lib\site-packages\gevent\server.py", line 233, in wrap_socket_and_handle
    with _closing_socket(self.wrap_socket(client_socket, **self.ssl_args)) as ssl_socket:
TypeError: wrap_socket() got an unexpected keyword argument 'cors_allowed_origins'
2020-09-09T08:16:07Z <Greenlet at 0x1812f1e8bf8: _handle_and_close_when_done(<bound method StreamServer.wrap_socket_and_handle , <bound method StreamServer.do_close
of <WSGIServer, (<gevent._socket3.socket [closed] at 0x1812fc41cc8)> failed with TypeError

这是跨域的问题,经测试发现,使用之前介绍过的 flask-cors 来处理,其实并不起作用,我们需要在 socketio 初始化的时候加入必要的参数

socketio = SocketIO()
socketio.init_app(app, cors_allowed_origins='*')

详细的信息可以参考这个链接, https://github.com/eventlet/eventlet/issues/526

源码下载

https://github.com/xugaoxiang/FlaskTutorial

Flask教程

更多 Flask 教程,请移步

https://xugaoxiang.com/category/python/flask/

参考资料

喜欢 (0)

您必须 登录 才能发表评论!

(4)个小伙伴在吐槽
  1. JS版本太久了,这条改一下就有了
    匿名2022-08-17 09:15
  2. 我下载的源码,按教程步骤操作,127.0.0.1:5000/push 成功出done!了,但127.0.0.1:5000没有出现test message
    匿名2022-07-16 20:36
  3. 为啥我按您的代码做了,第二个标签http://127.0.0.1:5000/回车,成功出done!了,但是第一个标签没有出现 test message !呢
    匿名2021-11-12 10:53