Search code examples
python-3.xwebsockettornadopython-asyncio

Send data from an other class through ws after client connected


I want to send data through websockets as soon as a client is connected. The Data is at an other place then the Websocket Handler. How can i get the data to the client ?

The server should hold the loop and the Handler. In the connector i connect to a tcp socket to get the data out of some hardware. I expect to have not more then 6 Websockets open once a time. The Data comes as a stream out of the TCP socket.

server.py

import os
from tornado import web, websocket
import asyncio
import connector


class StaticFileHandler(web.RequestHandler):

    def set_default_headers(self):
        self.set_header("Access-Control-Allow-Origin", "*")

    def get(self):
        self.render('index.html')


class WSHandler(websocket.WebSocketHandler):
    def open(self):
        print('new connection')
        self.write_message("connected")

    def on_message(self, message):
        print('message received %s' % message)
        self.write_message("pong")

    def on_close(self):
        print('connection closed')


public_root = 'web_src'

handlers = [
    (r'/', StaticFileHandler),
    (r'/ws', WSHandler),
]

settings = dict(
    template_path = os.path.join(os.path.dirname(__file__), public_root),
    static_path = os.path.join(os.path.dirname(__file__), public_root),
    debug = True
)

app = web.Application(handlers, **settings)

sensorIP = "xxx.xxx.xxx.xxx"

if __name__ == "__main__":
    app.listen(8888)
    asyncio.ensure_future(connector.main_task(sensorIP))
    asyncio.get_event_loop().run_forever()

connector.py

import yaml
import asyncio


class RAMReceiver:
    def __init__(self, reader):
        self.reader = reader
        self.remote_data = None
        self.initParams = None

    async def work(self):
        i = 0
        while True:
            data = await self.reader.readuntil(b"\0")
            self.remote_data = yaml.load(data[:-1].decode("utf-8", 
            "backslashreplace"))

            # here i want to emit some data
            # send self.remote_data to websockets 

            if i == 0:
                i += 1
                self.initParams = self.remote_data

                # here i want to emit some data after open event is 
                # triggered
                # send self.initParams as soon as a client has connected


async def main_task(host):
    tasks = []
    (ram_reader,) = await asyncio.gather(asyncio.open_connection(host, 
    51000))
    receiver = RAMReceiver(ram_reader[0])
    tasks.append(receiver.work())

while True:
    await asyncio.gather(*tasks)

Solution

  • You can use Tornado's add_callback function to call a method on your websocket handler to send the messages.

    Here's an example:

    1. Create an additional method on your websocket handler which will receive a message from connector.py and will send to connected clients:

    # server.py
    
    class WSHandler(websocket.WebSocketHandler):
    
        # make it a classmethod so that 
        # it can be accessed directly
        # from class without `self`
        @classmethod 
        async def send_data(cls, data):
             # write your code for sending data to client
    

    2. Pass the currently running IOLoop and WSHandler.send_data to your connector.py:

    # server.py
    
    from tornado import ioloop
    
    ...
    
    if __name__ == "__main__":
        ...
        io_loop = ioloop.IOLoop.current() # current IOLoop
        callback = WSHandler.send_data
        # pass io_loop and callback to main_task
        asyncio.ensure_future(connector.main_task(sensorIP, io_loop, callback))
        ...
    

    3. Then modify main_task function in connector.py to receive io_loop and callback. Then pass io_loop and callback to RAMReceiver.

    4. Finally, use io_loop.add_callback to call WSHandler.send_data:

    class RAMReceiver:
        def __init__(self, reader, io_loop, callback):
            ...
            self.io_loop = io_loop
            self.callback = callback
    
        async def work(self):
            ...
            data = "Some data"
            self.io_loop.add_callback(self.callback, data)
            ...