头图:https://cdn.naraku.cn/imgs/Flask-SocketIO.jpg
摘要:一个Flask-SocketIO小Demo

最近在做一个Flask程序,其中一个需求是前端传递参数,后端接收到后调用命令行,并将控制台打印的日志实时推送到前端显示。经过搜索得知想要实现该功能大概有2种方式:1种是利用调度工具Celery,另1种就是Websocket。

准备

  • 安装Flask-SocketIO

    1. $ pip install flask-socketio
  • 编写一个Flask程序

    1. from flask import Flask, render_template, request
    2. from flask_socketio import SocketIO, emit
    3. from threading import Lock
    4. import subprocess gevent
    5. async_mode = None
    6. app = Flask(__name__)
    7. app.config['SECRET_KEY'] = 'secret!'
    8. socketio = SocketIO(app)
    9. thread = None
    10. thread_lock = Lock()
    11. @app.route('/')
    12. def index():
    13. return render_template('index.html')

    交互

  • 其中poll()函数有如下返回值,这里判断状态不为None即判断为运行结束并跳出循环

    • 0, 正常结束
    • 1, sleep
    • 2, 子进程不存在
    • -15, Kill
    • None, 正在运行
      1. @socketio.on('imessage', namespace='/job')
      2. def ping(message):
      3. url = message['data']
      4. cmd = f'ping {url}'
      5. p = subprocess.Popen(cmd,
      6. stdin=subprocess.PIPE,
      7. stdout=subprocess.PIPE,
      8. stderr=subprocess.PIPE,
      9. shell=False)
      10. while True:
      11. for line in iter(p.stdout.readline, b''):
      12. line = line.decode('gbk')
      13. emit('message', {'data': line})
      14. if p.poll() is not None:
      15. break
  • 先与前端进行连接

    1. $(document).ready(function() {
    2. namespace = '/job';
    3. var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    4. // 连接后发送日志
    5. socket.on('connect', function(){
    6. console.log('[+] Connected')
    7. });
    8. });
  • 后端通过emit函数将内容发送到前端

    1. // 接收后端消息
    2. // 这里的message对应while循环中emit('message', {'data': line})
    3. socket.on('message', function(msg) {
    4. console.log(msg.data)
    5. $('#log').append('<br>' + $('<div/>').text(msg.data).html());
    6. });
  • 前端也可以通过emit向后端发送数据

    1. // 点击发送时将text框的内容发送到后端
    2. // 这里的imessage对应@socketio.on('imessage', namespace='/job')
    3. $('form#emit').submit(function(event) {
    4. socket.emit('imessage', {data: $('#emit_data').val()});
    5. return false;
    6. });

    代码

  • app.py

    1. from flask import Flask, render_template, request
    2. from flask_socketio import SocketIO, emit
    3. from threading import Lock
    4. import subprocess gevent
    5. async_mode = None
    6. app = Flask(__name__)
    7. app.config['SECRET_KEY'] = 'secret!'
    8. socketio = SocketIO(app)
    9. thread = None
    10. thread_lock = Lock()
    11. @app.route('/')
    12. def index():
    13. return render_template('index.html')
    14. @socketio.on('imessage', namespace='/job')
    15. def ping(message):
    16. url = message['data']
    17. cmd = f'ping {url}'
    18. p = subprocess.Popen(cmd,
    19. stdin=subprocess.PIPE,
    20. stdout=subprocess.PIPE,
    21. stderr=subprocess.PIPE,
    22. shell=False)
    23. while True:
    24. for line in iter(p.stdout.readline, b''):
    25. line = line.decode('gbk')
    26. emit('message', {'data': line})
    27. if p.poll() is not None:
    28. break
    29. if __name__ == '__main__':
    30. socketio.run(app)
  • index.html

    1. <!DOCTYPE html>
    2. <html>
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>SocketIO</title>
    6. <script src="//code.jquery.com/jquery-3.2.1.slim.min.js"></script>
    7. <script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.5/socket.io.min.js"></script>
    8. <script type="text/javascript">
    9. $(document).ready(function() {
    10. namespace = '/job';
    11. var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
    12.        // 连接后发送日志
    13. socket.on('connect', function(){
    14. console.log('[+] Connected')
    15. });
    16. // 接收后端消息
    17. socket.on('message', function(msg) {
    18. console.log(msg.data)
    19. $('#log').append('<br>' + $('<div/>').text(msg.data).html());
    20. });
    21. // 点击发送时将text框的内容发送到后端
    22. $('form#emit').submit(function(event) {
    23. socket.emit('imessage', {data: $('#emit_data').val()});
    24. return false;
    25. });
    26. });
    27. </script>
    28. </head>
    29. <body>
    30. <form id="emit" method="POST" action='#'>
    31. <input type="text" name="emit_data" id="emit_data" placeholder="Message">
    32. <input type="submit" value="发送">
    33. </form>
    34. <h2>Receive:</h2>
    35. <div id="log"></div>
    36. </body>
    37. </html>

    参考

  • Python中subprocess.Popen.poll

  • Flask-SocketIO官方文档翻译
  • 使用flask_socketio实现客户端间即时通信