Cliente Python Socket.IO para enviar mensajes de difusión al servidor TornadIO2

Estoy construyendo una aplicación web en tiempo real. Quiero poder enviar mensajes de difusión desde la implementación del lado del servidor de mi aplicación python.

Aquí está la configuración:

  • socketio.js en el lado del cliente
  • Servidor TornadIO2 como servidor Socket.IO
  • Python en el lado del servidor (framework Django )

Puedo enviar con éxito mensajes de socket.io del cliente al servidor. El servidor maneja estos y puede enviar una respuesta. A continuación describiré cómo hice eso.

Configuración actual y código

Primero, debemos definir una conexión que maneje los eventos socket.io:

class BaseConnection(tornadio2.SocketConnection): def on_message(self, message): pass # will be run if client uses socket.emit('connect', username) @event def connect(self, username): # send answer to client which will be handled by socket.on('log', function) self.emit('log', 'hello ' + username) 

El inicio del servidor se realiza mediante un método personalizado de administración de Django:

 class Command(BaseCommand): args = '' help = 'Starts the TornadIO2 server for handling socket.io connections' def handle(self, *args, **kwargs): autoreload.main(self.run, args, kwargs) def run(self, *args, **kwargs): port = settings.SOCKETIO_PORT router = tornadio2.TornadioRouter(BaseConnection) application = tornado.web.Application( router.urls, socket_io_port = port ) print 'Starting socket.io server on port %s' % port server = SocketServer(application) 

Muy bien, el servidor se ejecuta ahora. Añadamos el código de cliente:

  var sio = io.connect('localhost:9000'); sio.on('connect', function(data) { console.log('connected'); sio.emit('connect', '{{ user.username }}'); }); sio.on('log', function(data) { console.log("log: " + data); });  

Obviamente, {{ user.username }} será reemplazado por el nombre de usuario del usuario que ha iniciado sesión actualmente, en este ejemplo, el nombre de usuario es “alp”.

Ahora, cada vez que la página se actualiza, la salida de la consola es:

 connected log: hello alp 

Por lo tanto, invocar mensajes y enviar respuestas funciona. Pero ahora viene la parte difícil.

Problemas

La respuesta “hello alp” se envía solo al invocador del mensaje socket.io. Quiero transmitir un mensaje a todos los clientes conectados, para que puedan informarse en tiempo real si un nuevo usuario se une al participante (por ejemplo, en una aplicación de chat).

Asi que aqui están mis preguntas:

  1. ¿Cómo puedo enviar un mensaje de difusión a todos los clientes conectados?

  2. ¿Cómo puedo enviar un mensaje de difusión a varios clientes conectados que están suscritos en un canal específico?

  3. ¿Cómo puedo enviar un mensaje de difusión a cualquier lugar en mi código de Python (fuera de la clase BaseConnection )? ¿Esto requeriría algún tipo de cliente Socket.IO para python o está integrado en TornadIO2?

Todas estas transmisiones deben realizarse de manera confiable, por lo que supongo que los websockets son la mejor opción. Pero estoy abierto a todas las buenas soluciones.

Recientemente escribí una aplicación muy similar en una configuración similar, así que tengo varias ideas.

La forma correcta de hacer lo que necesita es tener un backend pub-sub. Hay tanto que puedes hacer con los ConnectionHandler simples. Con el tiempo, el manejo de conjuntos de conexiones de nivel de clase comienza a ponerse feo (sin mencionar el buggy).

Lo ideal sería usar algo como Redis, con enlaces asíncronos al tornado (echa un vistazo a brukva ). De esa manera, no tiene que meterse con el registro de clientes en canales específicos, Redis tiene todo eso listo para usar.

Esencialmente, tienes algo como esto:

 class ConnectionHandler(SockJSConnection): def __init__(self, *args, **kwargs): super(ConnectionHandler, self).__init__(*args, **kwargs) self.client = brukva.Client() self.client.connect() self.client.subscribe('some_channel') def on_open(self, info): self.client.listen(self.on_chan_message) def on_message(self, msg): # this is a message broadcast from the client # handle it as necessary (this implementation ignores them) pass def on_chan_message(self, msg): # this is a message received from redis # send it to the client self.send(msg.body) def on_close(self): self.client.unsubscribe('text_stream') self.client.disconnect() 

Tenga en cuenta que usé sockjs-tornado, que encontré mucho más estable que socket.io.

De todos modos, una vez que tenga este tipo de configuración, enviar mensajes desde cualquier otro cliente (como Django, en su caso) es tan fácil como abrir una conexión Redis ( redis-py es una apuesta segura) y publicar un mensaje:

 import redis r = redis.Redis() r.publish('text_channel', 'oh hai!') 

Esta respuesta resultó bastante larga, así que hice un esfuerzo adicional e hice una publicación de blog: http://blog.y3xz.com/blog/2012/06/08/a-modern-python-stack-for- Una aplicación web en tiempo real /

Escribo aquí, porque es difícil escribir en la sección de comentarios. Puede ver ejemplos de tornadoio2 en el directorio de ejemplos donde puede encontrar la implementación del chat, y:

 class ChatConnection(tornadio2.conn.SocketConnection): # Class level variable participants = set() def on_open(self, info): self.send("Welcome from the server.") self.participants.add(self) def on_message(self, message): # Pong message back for p in self.participants: p.send(message) 

Como se puede ver, implementaron a los participantes tal como fueron establecidos))

Si ya está usando django, por qué no echa un vistazo a gevent-socketio.