Как отправить сообщение с сервера клиенту с помощью Flask-Socket IO

Я пытаюсь создать приложение на Python, которое может отправлять сообщение с сервера клиенту. В настоящее время я использую этот образец кода здесь. Это приложение для чата, и оно работает нормально. Я попытался изменить приложение и добавить новую функцию в код 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()

Я вижу "полученное сообщение: пустышка" в терминале, но в веб-браузере ничего не изменилось.


person akmalhakimi1991    schedule 28.08.2017    source источник
comment
Трудно узнать, не имея полного примера для тестирования. Вы смотрели в консоли браузера, нет ли ошибок?   -  person Miguel    schedule 29.08.2017


Ответы (2)


У вас есть две ошибки, которые вам мешают:

Во-первых, вы пытаетесь создать событие с socket.io вне контекста сокета.

Когда функция обернута декоратором @socketio.on, она становится Event-Handlers. Пока событие запускается на стороне сервера, оно будет искать правый обработчик для обработки события и инициализировать контекст для конкретного клиента, создавшего событие.

Без инициализации этого контекста ваш socketio.emit('output', str(json), namespace='/chat') ничего не сделает, потому что сервер не знает, кому он должен отправить ответ.

В любом случае есть небольшая хитрость для отправки событий вручную определенному клиенту (даже если вы не находитесь в его контексте). Каждый раз, когда сокет открывается, сервер назначает его «частной» комнате с тем же именем, что и идентификатор сокета (sid). Итак, чтобы отправить сообщение клиенту вне контекста клиента, вы можете создать список идентификаторов подключенных клиентов и вызвать функцию emit с аргументом room=<id>.

Например:

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

После этого все должно работать нормально (сам пробовал). Сообщите мне, если у вас возникнут другие проблемы ...

person AlonP    schedule 28.08.2017
comment
Спасибо за ответ. Однако это все равно не сработало. В журнале консоли также нет ошибки. Просто вопрос, как лучше всего реализовать обновление python-html в реальном времени? Похоже, что у flask-socketio нет прямого способа сделать это. Мое единственное намерение состоит в том, чтобы модуль python выталкивал вывод из другого модуля python в html. - person akmalhakimi1991; 29.08.2017
comment
Поэтому я не понимаю, почему вы пытаетесь создать для этого отдельный поток. Можете ли вы дать мне пример потока, который вам нужен? Я думаю, вы можете начать свое действие из потока сервера socket.io, и только если вы хотите выполнить фоновую задачу, используйте функцию socketio.start_background_task. - person AlonP; 29.08.2017
comment
Извините, я новичок в flask. Причина, по которой я создаю отдельный поток, заключается в том, что строка web_app.socketio.run(web_app.app) блокировала дальнейший процесс. Итак, в настоящее время я разрабатываю приложение для общения между пользователем и компьютером, которое будет принимать речь пользователя в качестве входных данных ›транскрибировать ее в текст с помощью Watson STT› отправлять ее в Watson Conversation Service ›получать ответ от Watson› отображать ответ в Python. Эта часть уже сделана. Итак, теперь я планирую также отображать ввод (транскрибированный текст) и ответ от Watson на веб-странице (в реальном времени). - person akmalhakimi1991; 29.08.2017

Эти декораторы предназначены для простой загрузки страниц index.html и blah.html, они ничего не делают --- не передают никаких переменных и не записывают ничего в шаблон, они просто отображают его, в то время как все, что вы делаете, есть для командной строки, если вам нужно распечатать или передать что-либо, что должно быть в этих функциях:

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


@app.route('/blah/')
def blah():
    return render_template('blah.html')
person 0decimal0    schedule 28.08.2017