如何使用Flask-Socket IO从服务器向客户端发送消息

时间:2017-08-28 12:18:29

标签: python flask flask-socketio

我正在尝试创建一个可以从服务器向客户端发送消息的python应用程序。目前我正在使用here中的示例代码。这是一个聊天应用程序,它工作正常。我试图修改应用程序并在服务器端python代码中添加一个新函数,它会将“Dummy”消息​​打印到客户端,但看起来它不起作用。

这是我的HTML代码:

的index.html

<body>
    <ul id="messages"></ul>
    <ul id="output"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>

<script src="{{url_for('static', filename='assets/vendor/socket.io.min.js')}}"></script>
<script src="{{url_for('static', filename='assets/vendor/jquery.js')}}"></script>

<script>
  var socket = io.connect('http://127.0.0.1:5000/chat');

  $('form').submit(function(){
    socket.emit('chat message', $('#m').val());
    $('#m').val('');
    return false;
  });

  socket.on('chat message', function(msg){
    $('#messages').html($('<li>').text(msg));
  });

  socket.on('output', function(msg){
    alert(msg)
    $('#messages').html($('<li>').text(msg));
  });
</script>

这是我的后端代码:

web_app.py

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

app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app)
connected = False

def socket_onload(json):
    socketio.emit('output', str(json), namespace='/chat')
    print('received message: ' + str(json))


@socketio.on('chat message', namespace='/chat')
def handle_chat_message(json):
    print('received message: ' + str(json))
    emit('chat message', str(json), broadcast=True)


@socketio.on('connect')  # global namespace
def handle_connect():
    global connected
    connected = True
    print('Client connected')

@socketio.on('connect', namespace='/chat')
def handle_chat_connect():
    print('Client connected to chat namespace')
    emit('chat message', 'welcome!')

@socketio.on('disconnect', namespace='/chat')
def test_disconnect():
    print('Client disconnected')


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


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

main.py

import web_app
import threading
import time

def main():
    import web_app
    webapp_thread = threading.Thread(target=run_web_app)
    webapp_thread.start()
    # webapp_thread = threading.Thread(target=run_web_app, args=(i,))

    while web_app.connected==False:
        print "waiting for client to connect"
        time.sleep(1)
        pass

    print "Connected..."
    time.sleep(3)
    print "Trying to print dummy message..."
    web_app.socket_onload("Dummy")

def run_web_app():
    web_app.socketio.run(web_app.app)

if __name__ == '__main__':
    main()

我可以在终端看到“收到消息:虚拟”,但网络浏览器上没有任何变化。

2 个答案:

答案 0 :(得分:7)

您有两个错误,这些错误会阻止您这样做:

首先,您试图从套接字上下文中使用socket.io发出一个事件。

当一个函数被@socketio.on装饰器包裹时,它变为Event-Handlers。 在服务器端触发事件时,它将搜索正确的处理程序来处理事件并将上下文初始化为发出事件的特定客户端。

如果没有此上下文初始化,您的socketio.emit('output', str(json), namespace='/chat')将不会执行任何操作,因为服务器不知道它应该向谁回发响应。

无论如何,有一个小技巧可以将手动发送到特定客户端(即使您不在其上下文中)。每次打开套接字时,服务器都会将其分配给与套接字ID(sid)同名的“私人”会议室。因此,为了从客户端上下文向客户端发送消息,您可以创建已连接客户端ID的列表,并使用room=<id>参数调用emit函数。

例如:

<强> web_app.py:

...
from flask import Flask, request

clients = []

@socketio.on('connect')
def handle_connect():
    print('Client connected')
    clients.append(request.sid)

@socketio.on('disconnect')
def handle_disconnect():
    print('Client disconnected')
    clients.remove(request.sid)

def send_message(client_id, data):
    socketio.emit('output', data, room=client_id)
    print('sending message "{}" to client "{}".'.format(data, client_id))

...

然后您可能会使用以下内容:

<强> main.py:

import web_app
import threading
import time

def main():
    webapp_thread = threading.Thread(target=run_web_app)
    webapp_thread.start()

    while not web_app.clients:
        print "waiting for client to connect"
        time.sleep(1)

    print "Connected..."
    time.sleep(3)
    print "Trying to print dummy message..."
    web_app.send_message(web_app.clients[0], "Dummy")

...

但即使你试试这个,它也行不通(这会把我们带到第二个错误)。

第二,您正在将eventlet与常规Python线程混合在一起,这不是一个好主意。 eventlet使用的绿色线程与常规线程不兼容。相反,您应该使用绿色线程来满足所有线程需求。

我在互联网上找到的一个选项是修补Python标准库,以便将线程,套接字等替换为eventlet友好版本。您可以在main.py脚本的最顶部执行此操作:

import eventlet
eventlet.monkey_patch()

之后它应该工作正常(我自己尝试)。如果您有其他问题,请告诉我......

答案 1 :(得分:0)

这些装饰者只需加载页面index.htmlblah.html这些不做任何事情 - 不传递任何变量或不写任何内容他们只是渲染它的模板,同时你要做的所有事情都是命令行,如果你必须打印或传递它们必须在这些函数中的任何东西:

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


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