Search code examples
pythonwebsocket

Websockets Server Push in Python


How do I write a websockets server in Python which just pushes out data on a timed interval to all connected clients without waiting for any incoming messages ?


Solution

  • I'm answering my own question ...

    This is a working example of a Python websockets server which sends out a message to all clients every 5 seconds. I wrote this and managed to get it working as I could not find a single example of this on the web (March 2021)

    Hope this helps others, and if anyone has suggestions for improvements or better solutions using packages to maybe add ssl support or subscription type services additions, please write in the comments or answers section.

    import asyncio
    import logging
    import websockets
    from websockets import WebSocketServerProtocol
    import time
    import threading
    
    logging.basicConfig(level=logging.INFO)
    
    
    class Server:
    
        clients = set()
        logging.info(f'starting up ...')
    
        def __init__(self):
            logging.info(f'init happened ...')
            
        async def register(self, ws: WebSocketServerProtocol) -> None:
            self.clients.add(ws)
            logging.info(f'{ws.remote_address} connects')
    
        async def unregister(self, ws: WebSocketServerProtocol) -> None:
            self.clients.remove(ws)
            logging.info(f'{ws.remote_address} disconnects')
    
        async def send_to_clients(self, message: str) -> None:
            if self.clients:
                logging.info("trying to send")
                await asyncio.wait([client.send(message) for client in self.clients])
    
        async def ws_handler(self, ws: WebSocketServerProtocol, url: str) -> None:
            await self.register(ws)
            try:
                await self.distribute(ws)
            finally:
                await self.unregister(ws)
    
        async def distribute(self, ws: WebSocketServerProtocol) -> None:
            async for message in ws:
                await self.send_to_clients(message)
    
    
    async def timerThread(server,counter):
        counter = 0
        while True:
            await checkAndSend(server,counter)
            print("doing " + str(counter))
            time.sleep(5)
            counter = counter + 1
    
    async def checkAndSend(server,counter):
        # check something
        # send message
        logging.info("in check and send")
        await server.send_to_clients("Hi there: " + str(counter))
    
    # helper routine to allow thread to call async function
    def between_callback(server,counter):
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(timerThread(server,counter))
        loop.close()
    
    # start server
    server = Server()
    start_server = websockets.serve(server.ws_handler,'localhost',4000)
    counter = 0 
    
    # start timer thread
    threading.Thread(target=between_callback,args=(server,counter,)).start()
    
    # start main event loop
    loop = asyncio.get_event_loop()
    loop.run_until_complete(start_server)
    loop.run_forever()
    

    To see this working, you can use this simple html file as the client and then open the inspector to see the incoming messages on the Console log.

    <h1> Websocket Test </h1>
    <script>
    const ws = new WebSocket('ws://localhost:4000')
    ws.onopen = () => {
      console.log('ws opened on browser')
      ws.send('hello world')
    }
    ws.onmessage = (message) => {
      console.log(`message received`, message.data)
    }
    </script>
    

    If you are using python 3.11, the following function should be rewritten like this:

    async def send_to_clients(self, message: str) -> None:
        if self.clients:
            logging.info("trying to send")
            [await client.send(message) for client in self.clients]