如果另一个事件处理程序进程未完成,Flask-socketio应用程序不会从客户端处理新的emit事件

时间:2014-11-28 11:03:29

标签: websocket flask gevent flask-socketio

我想使用flask,flask-socketio来实现一个接受"命令字符串" (如:ping 8.8.8.8)在浏览器中,然后服务器执行它并实时将命令的输出返回给浏览器。

我的实施如下。

客户端发出' exec_event'事件发生到服务器,服务器运行命令并不断发出'exec_response'当命令生成新输出时,事件发生到客户端。

客户也可以发送' ctrlc_event'到服务器是否要终止命令。

我遇到的问题是:

服务器无法处理' ctrlc_event'当它处理前一个' exec_event'时。如果我直接杀死长期运行的命令,例如ping 8.8.8.8'在服务器上,然后服务器开始处理' ctrlc_event'。

有人能指出原因或给我一些指示吗?

服务器端代码

from gevent import monkey
monkey.patch_all()

from flask import Flask, render_template, session, Response
from flask.ext.wtf import Form
from wtforms import StringField, SubmitField
from wtforms.validators import DataRequired
from flask.ext.socketio import SocketIO, emit

import os
import time 
from subprocess import Popen, PIPE


class ExecForm(Form):
    command = StringField('Command: ', validators=[DataRequired()])
    submit = SubmitField('Execute')

class CtrlCForm(Form):
    submit = SubmitField('Ctrl C')


app = Flask(__name__)
app.debug = True
app.config['SECRET_KEY'] = 'hard-to-guess'
socketio = SocketIO(app)

@app.route('/')
def index():
    exec_form = ExecForm()
    ctrlc_form = CtrlCForm()
    return render_template('index.html', 
                           exec_form=exec_form, 
                           ctrlc_form=ctrlc_form)


@socketio.on('exec_event', namespace='/test')
def execute(message):
    command = message['data']
    process = Popen(command, shell=True, stdout=PIPE, stderr=PIPE,
                    close_fds=True, preexec_fn=os.setsid)

    session['pid'] = process.pid

    for out in iter(process.stdout.readline, ''):
        out = '<pre>' + out + '</pre>'
        emit('exec_response', {'data': out})
        time.sleep(0.001)

    out = "Command finished." + '<br /><br />'
    emit('exec_terminate', {'data': out})


@socketio.on('ctrlc_event', namespace='/test')
def ctrlc(message):
    print("Received ctrlc")
    # do something to kill the process
    # finally emit an event.
    out = "Process killed." + '<br /><br/>'
    emit('ctrlc_response', {'data': out})


if __name__ == '__main__':
    socketio.run(app, host='0.0.0.0', port=8080)

客户端代码

<html>
<head>
<title>Example</title>
<script type="text/javascript" src='//code.jquery.com/jquery-1.11.0.min.js'></script>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/socket.io/0.9.16/socket.io.min.js"></script>
<script type="text/javascript">
$(document).ready(function(){
    var socket = io.connect('http://' + document.domain + ':' + location.port + '/test');

    $('#exec_form').submit(function(event) {
        socket.emit('exec_event', {data: $('#exec_input').val()});
        return false;
    }); 

    $('#ctrlc_form').submit(function(event) {
        socket.emit('ctrlc_event', {data: 'nothing'});
        return false;
    }); 

    socket.on('exec_response', function(msg) {
        $('#result').append(msg.data);
    }); 

    socket.on('exec_terminate', function(msg) {
        $('#result').append(msg.data);
    });

    socket.on('ctrlc_response', function(msg) {
        $('#result').append(msg.data);
    });
});
</script>
<style>
.result {
    overflow-y: scroll;
    width: 100%;
    height: 400px;
    border: 4px solid red;
}
.result pre {
    margin: 2px;
}
</style>

</head>
<body>
    <h3>Flask Example</h3>
    <form id="exec_form" method='POST' action='#' target='result'>        
        {{ exec_form.command.label }} 
        {{ exec_form.command(id="exec_input", placeholder="command", size=100) }} 
        {{ exec_form.submit(id="exec_btn") }}
    </form>
    <form id="ctrlc_form" method='POST' action='#' target='result'>
        {{ ctrlc_form.submit(id="ctrlc_btn") }}
    </form>
    <div id="result" class="result"></div>
</body>
</html>

1 个答案:

答案 0 :(得分:0)

问题是由gevent-socketio服务器的结构方式引起的。当服务器从客户端收到一个事件时(假设你的exec_event),它会通过调用适当的处理程序来调度事件。然后该线程将运行事件处理程序,并且只有处理程序返回它后才会返回检查新客户端发起的事件。

解决方案是将ctrlc_event发送到不同的通信渠道,而不是用于exec_event的渠道。例如,在不同的命名空间上打开两个连接,然后每个连接将获得自己的服务器线程。