公司的项目要求使用flask+socketio+python
完成web服务端的开发,项目很简单,但由于第一次做web相关开发,遇到很多坑,主要问题是flask-socketio的异步模式。
服务端需求:
1.与下位机进行tcp数据通信(异步),下位机的传感器2微妙产生一组数据,所以数据量比较大
2.将下位机数据同步到前端绘图(flask-socketio)
3.数据存储(mysql+ json文件)
4.数据定期清除任务采用flask-apscheduler框架
服务端的结构:
1.tcp异步IO用来与下位机数据通信
2.使用socketio线程与前端数据通信,flask框架处理浏览器的用户请求
3.数据处理线程用作数据处理和存储
1.在外部线程使用socketio对象emit事件失败:
flask app上下文的问题,解决方案参见如下链接
https://segmentfault.com/q/1010000012605133/a-1020000012616493
2.无法与socketio对端建立连接: 使用的socketio测试工具如下:
http://amritb.github.io/socketio-client-tool/#url=bG9jYWxob3N0OjUwMDA=&opt=&events=?nsukey
socketio异步模式的选择,按照官方文档,
sync_mode: The asynchronous model to use. See the Deployment
section in the documentation for a description of the
available options. Valid async modes are
``threading``, ``eventlet``, ``gevent`` and
``gevent_uwsgi``. If this argument is not given,
``eventlet`` is tried first, then ``gevent_uwsgi``,
then ``gevent``, and finally ``threading``. The
first async mode that has all its dependencies installed
is then one that is chosen.
异步模式有这么几种:eventlet, gevent,…,threading
1. 方案一:
async_mode = "threading"
app = Flask(__name__)
socketio = SocketIO(app, async_mode=async_mode, cors_allowed_origins="*")
经测试,threading的异步模式无法使用socketio测试工具测试,但实际上可以与web前端进行通信。
2. 方案二:
采用了eventlet的异步模式,使用猴子补丁。
async_mode = "eventlet"
app = Flask(__name__)
socketio = SocketIO(app, async_mode=async_mode, cors_allowed_origins="*")
这时我们可以用socketio测试工具建立连接了,代码请参考官方示例。
刚才我说的这个服务端还有一个异步的tcp的服务线程,和eventlet的绿色线程(协程)矛盾了,导致服务线程产生的数据无法通过socketio发给前端(仍然不知为何)。
问题描述在此:
https://stackoverflow.com/questions/36385286/flask-socketio-eventlet-cannot-switch-to-a-different-thread
所以,折腾半天,还是要使用threading异步模式…
使用eventlet的异步模式+flask-apscheduler时,
apscheduler执行一次定时任务后会造成阻塞,以致于flask服务进程无法收到前端http请求,
和问题二类似,也是eventlet和多线程的问题,通过使用threading的异步模式解决了,
原因是apscheduler会以阻塞模式执行任务,而eventlet实际上是使用协程,虽然使用的是BackgroundScheduler,也没用。
在stackoverflow上有相关问题的回答(我找不到了),总结一下就是
eventlet不要和apscheduler一起用…
代码如下:
async_mode = "threading"
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
app.config['SCHEDULER_API_ENABLED'] = True
jobstores = {
'default': SQLAlchemyJobStore(url="sqlite:///jobs.sqlite")
}
app.config["SCHEDULER_JOBSTORES"] = jobstores
sched = APScheduler()
sched.init_app(app)
sched.start()
socketio = SocketIO(app, async_mode=async_mode, cors_allowed_origins="*")