Flask + Flask-socketio 实现简单的 WebServer(可与C++程序进行通信)

Flask + Flask-socketio 实现简单的 WebServer

最近手头上的项目要给树莓派上的一个程序实现一个web客户端,公司之前用的goahead(c++)实现的,正好最近在看Python,鉴于python有强大的web开发框架,所以决定用python来实现这一模块,达到增强C++程序的目的。
初步的思路是通过python的框架 Flask来处理web的请求,将处理好的数据转发给linux上的C++程序, C++程序处理好之后再将数据发送回web。
对于C++与python之间的通信之前想的是通过互相调用的方式,但是鉴于这样的方式比较麻烦。所以改为C++和python两个进程之间的通信

想了解C++和python之间的通信的看这边:
http://blog.csdn.net/lht_okk/article/details/52810228
http://blog.csdn.net/taiyang1987912/article/details/44779719

最初的构思是通过SOCKET来实现两个进程间的通信,socket也是网络上两个进程间通信的常用方法,但是介于C++和Python的程序是运行于同一个系统下的,用socket有点杀鸡用牛刀的意思,所以改为了用linux内核提供的消息队列来实现。

想了解linux下Sokcet的看这边:https://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html
想了解python下socket的看这边:https://www.cnblogs.com/aylin/p/5572104.html

实现进程间通信的方法有:管道、信号量、共享内存、消息队列。
最开始是想用共享内存来做,然后通过信号量进行同步和互斥操作。结果发现消息队列每一条消息是可以携带数据的。。。白忙活了一天,只恨自己积累不够。。

想了解linux进程间通信的可以看这边: 这位博主写的非常通俗易懂!强烈建议学习一下!
共享内存:http://blog.csdn.net/ljianhui/article/details/10253345
信号量:http://blog.csdn.net/ljianhui/article/details/10243617
命名管道:http://blog.csdn.net/ljianhui/article/details/10202699
消息队列:http://blog.csdn.net/ljianhui/article/details/10287879 本文采用的方式
套接字(socket):http://blog.csdn.net/ljianhui/article/details/10697935

在我个人的理解上,消息队列其实就是在共享内存上进行一层封装使其更方便使用,那么为什么还要有共享内存这个方式?因为这几种进程间的通信都是使用系统资源的,使用系统资源,那么自然有限制存在。
linux下查看各个资源限制的命令为: ipcs -l

Flask + Flask-socketio 实现简单的 WebServer(可与C++程序进行通信)_第1张图片

可以看到我的机器上消息队列的单个消息最大长度为8192bytes, 最大队列大小为16384bytes, 而共享内存最大可以达到17112760316.。反正就是大得多。。所以在用来传输比较大的文件的时候消息队列是不能满足条件的,可以申请一段共享内存来实现。

查看当前各个资源的情况可以用: ipcs -u
Flask + Flask-socketio 实现简单的 WebServer(可与C++程序进行通信)_第2张图片
当前我申请了2个消息队列,使用的空间为8192bytes,使用的消息头为8.
调试的时候可以通过 ipcrm -q (消息队列id) 来删除创建了的消息队列,ipcrm也可以用来删除信号量及共享内存。注意消息队列需要在程序结束时进行删除否则会一直内核中,下次再创建的时候如果队列中还有未处理完的消息,会优先处理。


进程间通信的方法有了,那么具体是怎么使用的呢?这边我的做法是将消息队列进行了简单的封装,编译成so库,让python程序及C++的程序去调用,这样就独立开了C++程序,实现了一个单独的模块。
(其实封装只是申请了两个消息队列A,B,C++程序将消息放进A队列,从B队列中取消息, python程序刚好相反。这样就实现了两者之间的通信。)

这边涉及到一个知识,就是python如何调用so库,我使用的是用python的模块Ctypes。具体的调用可以看代码。

想了解CTypes的可以看这边:http://blog.csdn.net/linda1000/article/details/12623527

有一个细节就是要获取char数组需要用 s = create_string_buffer(1024) 这里的1024就是这个数组的大小,而要将这个值转化为python的str只需要调用s.value 即可。这里要注意!如果是s.value的话,是将s当成\0结尾的字符串来输出它的内容,所以如果打算用char[]来传递数据的话,中间有一个字节为\x00的话就会被截断!!这个时候需要用s.raw这个属性来获取这个数组的全部内容!!

还有一个就是既然是数据间的传输那么肯定要有一种数据类型,因为python有内置的强大的json模块,所以决定要json来作为数据传输的标准。

那么C++程序也需要解析Json,这里我选择了Rapdijson,它可以很方便的解析和生成json字符串。使用及移植非常方便,因为它只有头文件!!把头文件文件夹拷贝下来就可以用!编译都不需要!

想了解Rapdijson的可以看这边:http://rapidjson.org/zh-cn/md_doc_tutorial_8zh-cn.html

到此已经实现了python及C++之间的通信,下一步就是实现web和python之间的通信了。


我选择的web框架是Flask。。。为什么没有用Django。。因为我朋友说我项目比较小。Flask够用了。。由于Flask的文档不算很多。。直接导致了后面我踩了两天的坑,也怪自己看代码不仔细,在这里总结下使用过程中遇到的一些问题。
一些基础的Flask的使用这里就不在赘述,可以查看Flask的官方文档,这里提供了一位大佬的翻译的中文版:

想了解Flask的看这边:http://docs.jinkan.org/docs/flask/index.html

光有Flask还是不够的,因为它只能处理普通的GET及POST请求,而我们知道Http是短连接,要实现server主动向web发送数据有以下几种方法:1、Ajax轮训 2、WebSocket 3、Http1.1
这里我使用的是WebSocket。因为它简单。。。

想了解websocket可以看这边:
http://www.ruanyifeng.com/blog/2017/05/websocket.html?utm_source=tuicool&utm_medium=referral

其实websocket就是在http的基础上建立了一个长连接,这样两者之间就可以互相发消息.
因为用底层的接口编写相对繁琐,所以我前端用的是socket.io.js
你可以在这里获取到:
https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.4/socket.io.js

server端用的是Flask的扩展模块:Flask-socketio
这里有他的官方文档:(内容相对简单,英文也勉强可以看看)
http://flask-socketio.readthedocs.io/en/latest/

这里记录下我猜的坑:
因为我要实现C++程序主动发消息给web,所以python程序中需要有一个线程来监听C++程序的消息队列。
开始我使用的是threading.Thread来创建的线程,发觉怎么都不能将消息发送出去,虽然程序可以正常运行到socketio.emit()
后面发觉Flask自己封装了一个创建子线程的接口socketio.start_background_task(func),这样线程是正常创立了但是还是不可以发送消息回去。在经历整整一天的各种查找之后。。。原来是因为我线程函数中的sleep用的是time.sleep() ,而正确的sleep应该是:socketio.sleep。。。
这里附上正确开启线程的代码:

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
#注意这里将async_mode改成"threading"也即使不用socketio.sleep()也可以正常运行,但是运行之后server这边一直提示socket运行失败,实际上是改成了轮训,也可以实现互相通信。但是我们要用的是websocket,所以这种方式不可取。
socketio = SocketIO(app, async_mode=None)

@socketio.on('connect')
def connect():
    print('one client connect')
    socketio.start_background_task(target=recvThread)

def recvThread():
    while True:
        socketio.sleep(4)
        emit("response")

经过后续的学习,发觉用threading.Thread创建的线程也是可以正常进行发送的,关键点在于要用socketio.sleep()

到这边已经可以实现了web和linux上C++程序的双向通信,下面记录下前端开发过程中遇到的一些问题。


对于一个web界面必不可少的就是上传文件,在这里我也需要上传文件到树莓派中,用于程序的升级功能。

所幸Flask自带有关于上传的增强功能.Flask-uploads

想了解Flask-uploads的可以看这里https://zhuanlan.zhihu.com/p/23731819?refer=flask

对于前端部分,因为原生的功能较少,所以使用了一个简单的上传JS脚本:Dropzone.js. 这是用原生JS编写的,依赖项很少。

想了解Dropzone.js的可以看这里:
https://segmentfault.com/a/1190000010871341#articleHeader5
http://www.renfei.org/blog/dropzone-js-introduction.html

最后附上整个项目的代码:
https://github.com/StartAt24/Python-Flask

做的比较简单,功能相对单一,有疑问欢迎留言交流。

你可能感兴趣的:(Flask)