【问题标题】:How to send message from server to client using Flask-Socket IO如何使用 Flask-Socket IO 从服务器向客户端发送消息
【发布时间】:2018-02-05 17:06:20
【问题描述】:

我正在尝试创建一个可以将消息从服​​务器发送到客户端的 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')

ma​​in.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()

我可以在终端中看到“received message: Dummy”,但网络浏览器上没有任何变化。

【问题讨论】:

  • 如果没有完整的示例进行测试,很难知道。您是否查看浏览器的控制台以查看是否有任何错误?

标签: python flask flask-socketio


【解决方案1】:

你有两个错误阻止你这样做:

首先,您正尝试在套接字上下文之外使用 socket.io 发出一个事件。

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

如果没有初始化这个上下文,你的socketio.emit('output', str(json), namespace='/chat') 将什么都不做,因为服务器不知道它应该向谁发回响应。

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

例如:

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))

...

那么你可能会这样使用:

ma​​in.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()

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

【讨论】:

  • 感谢您的回答。然而它仍然没有工作。控制台日志中也没有错误。只是一个问题,实现实时 python-html 更新的最佳方式/库是什么?似乎flask-socketio 没有直接的方法来做到这一点。我唯一的目的是让一个 python 模块将另一个 python 模块的输出推送到 html。
  • 所以我不明白你为什么要为此创建一个单独的线程。你能给我一个你需要的示例流程吗?我认为您可以从 socket.io 服务器线程开始您的操作,并且只有在您想执行后台任务时才使用 socketio.start_background_task 函数。
  • 对不起,我对烧瓶很陌生。我创建一个单独线程的原因是因为web_app.socketio.run(web_app.app) 行阻止了进一步的进程。所以目前我正在开发一个用户-计算机“对话”应用程序,它将用户语音作为输入 > 使用 Watson STT 将其转录为文本 > 将其发送到 Watson Conversation Service > 从 Watson 获取响应 > 在 Python 中显示响应。这部分已经完成。所以现在,我还计划将输入(转录文本)和来自 Watson 的响应显示到网页中(实时)
  • 我为您找到了确切的解决方案!使用 Socket.io 签出“使用消息队列”(链接 here)。这将使您能够从服务器以外的进程向客户端发出事件。我之前没有尝试过,但它看起来非常强大。阅读我给你的链接中的文档并在互联网上搜索更多示例......祝你好运! :)
  • 有没有比“sid”更好的方法?也许在 javascript 而不是烧瓶中?
【解决方案2】:

这些装饰器用于简单地加载页面 index.html 和 blah.html 这些不做任何事情 --- 不传递任何变量或不向模板写入任何内容,它们只是渲染它,同时所有如果您必须打印或传递这些函数中必须包含的任何内容,那么您正在做的事情就是命令行:

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


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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-11-10
    • 2017-04-06
    • 2015-04-25
    • 1970-01-01
    • 2013-06-24
    • 2017-01-12
    • 1970-01-01
    相关资源
    最近更新 更多