question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Several Websocket servers (in the same process)

See original GitHub issue

In WebsocketServer class, the clients attribute is defined at the class level, not as an instance attribute. Is there any good reason for this design?

This means that when you use several WebsocketServer (on different ports) and use the send_message_to_all method on a server instance, the message is sent to all clients, including the clients that connected to another server !

The fix is trivial and I can make a Pull Request if this could be changed.

Thanks for this very useful lib BTW ! 😄

Issue Analytics

  • State:closed
  • Created 5 years ago
  • Comments:8 (1 by maintainers)

github_iconTop GitHub Comments

1reaction
kdschlossercommented, Oct 3, 2018

all code blocks are pseudo code. They have not been tested specifically in the websocket_server library

OK so this an idea that works in a test setup of simple class instances and it should as well in the websocket_server lib.

class API():

    def run_forever(self):
        try:
            logger.info("Listening on port %d for clients.." % self.port)
            self.serve_forever()
        except KeyboardInterrupt:
            self.server_close()
            logger.info("Server terminated.")
        except Exception as e:
            logger.error(str(e), exc_info=True)
            exit(1)

    def new_client(self, client, server):
        pass

    def client_left(self, client, server):
        pass

    def message_received(self, client, server, message):
        pass

    def set_fn_new_client(self, fn):
        self.new_client = fn

    def set_fn_client_left(self, fn):
        self.client_left = fn

    def set_fn_message_received(self, fn):
        self.message_received = fn

    def send_message(self, client, msg):
        self._unicast_(client, msg)

    @classmethod
    def send_message_to_all(cls, msg):
        self._multicast_(msg)


class WebsocketServer(ThreadingMixIn, TCPServer, API):
    allow_reuse_address = True
    daemon_threads = True  # comment to keep threads alive until finished

    clients = []
    id_counter = 0

    def __init__(self, port, host='127.0.0.1', loglevel=logging.WARNING):
        self.send_message_to_all = self.__send_message_to_all
        logger.setLevel(loglevel)
        TCPServer.__init__(self, (host, port), WebSocketHandler)
        self.port = self.socket.getsockname()[1]

    def __send_message_to_all(self, msg):
        for client in self.clients:
            if client['server'] == self:
                self._unicast_(client, msg)

    def _message_received_(self, handler, msg):
        self.message_received(self.handler_to_client(handler), self, msg)

    def _ping_received_(self, handler, msg):
        handler.send_pong(msg)

    def _pong_received_(self, handler, msg):
        pass

    def _new_client_(self, handler):
        self.id_counter += 1
        client = {
            'id': self.id_counter,
            'handler': handler,
            'address': handler.client_address,
            'server': self
        }
        self.clients.append(client)
        self.new_client(client, self)

    def _client_left_(self, handler):
        client = self.handler_to_client(handler)
        self.client_left(client, self)
        if client in self.clients:
            self.clients.remove(client)

    def _unicast_(self, to_client, msg):
        to_client['handler'].send_message(msg)

    def _multicast_(self, msg):
        for client in self.clients:
            self._unicast_(client, msg)

    def handler_to_client(self, handler):
        for client in self.clients:
            if client['handler'] == handler:
                return client

Now what i did here was I had the instance override the class method of the parent class. This way it can be used both ways.

from websocket_server import WebsocketServer

instance1 = WebsocketServer(1234)
instance2 = WebsocketServer(4321)

instance1.send_message_to_all('message sent to only instance1 clients')
WebsocketServer.send_message_to_all('message sent to all clients on all websocket servers')

I think this would be the most elegant solution. and the one that makes the most sense.

being able to access one instance of a websocket server from another instance I do not believe is the best idea. You may even consider changing the clients container to have a mangled name. Then add a property that will iterate the mangled name container and only return clients that pertain to it.

In the original code there was no way to tell what client belong to what websocket connection even if you iterated over the devices container. No marker that was easily had. Without really digging into it but I think that client[‘handler’].server might point back to the websocket instance, but I am not sure. Event if you do not make clients a mangled name at least make it a private one.

This adds the mangled name which offers protection against inadvertently sending data to the wrong websocket connection if accessed from a websocket instance.

class WebsocketServer(ThreadingMixIn, TCPServer, API):
    allow_reuse_address = True
    daemon_threads = True  # comment to keep threads alive until finished

    __clients = []
    id_counter = 0

    def __init__(self, port, host='127.0.0.1', loglevel=logging.WARNING):
        self.send_message_to_all = self.__send_message_to_all
        logger.setLevel(loglevel)
        TCPServer.__init__(self, (host, port), WebSocketHandler)
        self.port = self.socket.getsockname()[1]
    
    @property
    def clients(self):
        return list(
            client for client in self.__clients
            if client['server'] == self
        )    

    def __send_message_to_all(self, msg):
        for client in self.clients:
            self._unicast_(client, msg)

    def _message_received_(self, handler, msg):
        self.message_received(self.handler_to_client(handler), self, msg)

    def _ping_received_(self, handler, msg):
        handler.send_pong(msg)

    def _pong_received_(self, handler, msg):
        pass

    def _new_client_(self, handler):
        self.id_counter += 1
        client = {
            'id': self.id_counter,
            'handler': handler,
            'address': handler.client_address,
            'server': self
        }
        self.__clients.append(client)
        self.new_client(client, self)

    def _client_left_(self, handler):
        client = self.handler_to_client(handler)
        self.client_left(client, self)
        if client in self.__clients:
            self.__clients.remove(client)

    def _unicast_(self, to_client, msg):
        to_client['handler'].send_message(msg)

    def _multicast_(self, msg):
        for client in self.__clients:
            self._unicast_(client, msg)

    def handler_to_client(self, handler):
        for client in self.__clients:
            if client['handler'] == handler:
                return client
0reactions
Pithikoscommented, Jul 15, 2021

So the original design simply dealt with a single instance. But all this makes sense so v0.5.1 should have this fixed.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Multiple websocket servers on one machine - Stack Overflow
Is there anyway to use multiple socket cluster running on same machine on same port? No. Not directly. You need some sort of...
Read more >
A Simple Multi-Client WebSocket Server - Forty Years of Code
Just four methods are needed for a complete, correct echo server: Start and Stop , which are self-explanatory, Listen which implements the HTTP ......
Read more >
Writing WebSocket servers - Web APIs | MDN
WebSocket servers are often separate and specialized servers (for load-balancing or other practical reasons), so you will often use a reverse ...
Read more >
WebSocket - The Modern JavaScript Tutorial
On the other hand, wss:// is WebSocket over TLS, (same as HTTPS is HTTP over TLS), ... else { // e.g. server process...
Read more >
Using multiple nodes | Socket.IO
the WebSocket transport does not have this limitation, since it relies on a single TCP connection for the whole session. Which means that...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found