Websockets con Tornado: obtenga acceso desde el “exterior” para enviar mensajes a los clientes

Estoy empezando a entrar en WebSockets como una forma de enviar datos de un servidor a clientes conectados. Ya que uso Python para progtwigr cualquier tipo de lógica, miré Tornado hasta ahora. El siguiente fragmento muestra el ejemplo más básico que se puede encontrar en cualquier lugar de la Web:

import tornado.httpserver import tornado.websocket import tornado.ioloop import tornado.web class WSHandler(tornado.websocket.WebSocketHandler): def open(self): print 'new connection' self.write_message("Hello World") def on_message(self, message): print 'message received %s' % message self.write_message('ECHO: ' + message) def on_close(self): print 'connection closed' application = tornado.web.Application([ (r'/ws', WSHandler), ]) if __name__ == "__main__": http_server = tornado.httpserver.HTTPServer(application) http_server.listen(8888) tornado.ioloop.IOLoop.instance().start() 

Como sea, esto funciona como se pretende. Sin embargo, no puedo entender cómo puedo “integrar” esto en el rest de mi aplicación. En el ejemplo anterior, el WebSocket solo envía algo a los clientes como respuesta al mensaje de un cliente. ¿Cómo puedo acceder al WebSocket desde el “exterior”? Por ejemplo, para notificar a todos los clientes actualmente conectados que se ha producido algún evento de tipo, y este evento NO es ningún tipo de mensaje de un cliente. Idealmente, me gustaría escribir en algún lugar de mi código algo como:

 websocket_server.send_to_all_clients("Good news everyone...") 

¿Cómo puedo hacer esto? ¿O tengo una completa confusión sobre cómo se supone que funcionan WebSockets (o Tornado)? ¡Gracias!

Esto se basa en el ejemplo de Hans Then. Esperemos que le ayude a comprender cómo puede hacer que su servidor inicie la comunicación con sus clientes sin que los clientes activen la interacción.

Aquí está el servidor:

 #!/usr/bin/python import datetime import tornado.httpserver import tornado.websocket import tornado.ioloop import tornado.web class WSHandler(tornado.websocket.WebSocketHandler): clients = [] def open(self): print 'new connection' self.write_message("Hello World") WSHandler.clients.append(self) def on_message(self, message): print 'message received %s' % message self.write_message('ECHO: ' + message) def on_close(self): print 'connection closed' WSHandler.clients.remove(self) @classmethod def write_to_clients(cls): print "Writing to clients" for client in cls.clients: client.write_message("Hi there!") application = tornado.web.Application([ (r'/ws', WSHandler), ]) if __name__ == "__main__": http_server = tornado.httpserver.HTTPServer(application) http_server.listen(8888) tornado.ioloop.IOLoop.instance().add_timeout(datetime.timedelta(seconds=15), WSHandler.write_to_clients) tornado.ioloop.IOLoop.instance().start() 

Hice la lista de clientes una variable de clase, en lugar de global. En realidad no me importaría usar una variable global para esto, pero como estaba preocupado por esto, aquí hay un enfoque alternativo.

Y aquí hay un ejemplo de cliente:

 #!/usr/bin/python import tornado.websocket from tornado import gen @gen.coroutine def test_ws(): client = yield tornado.websocket.websocket_connect("ws://localhost:8888/ws") client.write_message("Testing from client") msg = yield client.read_message() print("msg is %s" % msg) msg = yield client.read_message() print("msg is %s" % msg) msg = yield client.read_message() print("msg is %s" % msg) client.close() if __name__ == "__main__": tornado.ioloop.IOLoop.instance().run_sync(test_ws) 

A continuación, puede ejecutar el servidor y conectar dos instancias del cliente de prueba. Cuando lo haces, el servidor imprime esto:

 bennu@daveadmin:~$ ./torn.py new connection message received Testing from client new connection message received Testing from client <15 second delay> Writing to clients connection closed connection closed 

El primer cliente imprime esto:

 bennu@daveadmin:~$ ./web_client.py msg is Hello World msg is ECHO: Testing from client < 15 second delay> msg is Hi there! 0 

Y el segundo imprime esto:

 bennu@daveadmin:~$ ./web_client.py msg is Hello World msg is ECHO: Testing from client < 15 second delay> msg is Hi there! 1 

A los fines del ejemplo, acabo de hacer que el servidor envíe el mensaje a los clientes con un retraso de 15 segundos, pero podría ser activado por cualquier cosa que desee.

Es necesario realizar un seguimiento de todos los clientes que se conectan. Asi que:

 clients = [] def send_to_all_clients(message): for client in clients: client.write_message(message) class WSHandler(tornado.websocket.WebSocketHandler): def open(self): send_to_all_clients("new client") clients.append(self) def on_close(self): clients.remove(self) send_to_all_clients("removing client") def on_message(self, message): for client in clients: if client != self: client.write_message('ECHO: ' + message) 

Mi solución para esto: primero agregue "if __name__ == '__main__':" – a main.py. luego importar main.py en el módulo websocket. Por ejemplo, ( import main as MainApp ). ahora es posible llamar a una función en ‘main.py’ desde ws.py/WebSocketHandler-function. – dentro del controlador, pase el mensaje así: MainApp.function(message)

No sé si esto es lo contrario de elegante pero funciona para mí.

..plus crea e importa un ‘config.py’ personalizado (así se ve: someVar = int(0) ) en el ‘mainApp.py’ .. así: import config as cfg -> ahora puedes alterar variables con cfg.someVar = newValue desde dentro de la función en ‘main.py’ que una vez fue llamado por el controlador desde ‘ws.py’.