Python SocketServer: sending to multiple clients?

后端 未结 4 452
太阳男子
太阳男子 2020-12-03 08:27

Well, I\'m trying to build a small python prgram with a SocketServer that is supposed to send messages it receives to all connected clients. I\'m stuck, I don\'t know how to

相关标签:
4条回答
  • 2020-12-03 09:07

    To take multiple clients simultaneously, you will have to add SocketServer.ForkingMixIn or ThreadingMixIn.

    0 讨论(0)
  • 2020-12-03 09:12

    You want to look at asyncore here. The socket operations you're calling on the client side are blocking (don't return until some data is received or a timeout occurs) which makes it hard to listen for messages sent from the host and let the client instances enqueue data to send at the same time. asyncore is supposed to abstract the timeout-based polling loop away from you.

    Here's a code "sample" -- let me know if anything is unclear:

    from __future__ import print_function
    
    import asyncore
    import collections
    import logging
    import socket
    
    
    MAX_MESSAGE_LENGTH = 1024
    
    
    class RemoteClient(asyncore.dispatcher):
    
        """Wraps a remote client socket."""
    
        def __init__(self, host, socket, address):
            asyncore.dispatcher.__init__(self, socket)
            self.host = host
            self.outbox = collections.deque()
    
        def say(self, message):
            self.outbox.append(message)
    
        def handle_read(self):
            client_message = self.recv(MAX_MESSAGE_LENGTH)
            self.host.broadcast(client_message)
    
        def handle_write(self):
            if not self.outbox:
                return
            message = self.outbox.popleft()
            if len(message) > MAX_MESSAGE_LENGTH:
                raise ValueError('Message too long')
            self.send(message)
    
    
    class Host(asyncore.dispatcher):
    
        log = logging.getLogger('Host')
    
        def __init__(self, address=('localhost', 0)):
            asyncore.dispatcher.__init__(self)
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self.bind(address)
            self.listen(1)
            self.remote_clients = []
    
        def handle_accept(self):
            socket, addr = self.accept() # For the remote client.
            self.log.info('Accepted client at %s', addr)
            self.remote_clients.append(RemoteClient(self, socket, addr))
    
        def handle_read(self):
            self.log.info('Received message: %s', self.read())
    
        def broadcast(self, message):
            self.log.info('Broadcasting message: %s', message)
            for remote_client in self.remote_clients:
                remote_client.say(message)
    
    
    class Client(asyncore.dispatcher):
    
        def __init__(self, host_address, name):
            asyncore.dispatcher.__init__(self)
            self.log = logging.getLogger('Client (%7s)' % name)
            self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
            self.name = name
            self.log.info('Connecting to host at %s', host_address)
            self.connect(host_address)
            self.outbox = collections.deque()
    
        def say(self, message):
            self.outbox.append(message)
            self.log.info('Enqueued message: %s', message)
    
        def handle_write(self):
            if not self.outbox:
                return
            message = self.outbox.popleft()
            if len(message) > MAX_MESSAGE_LENGTH:
                raise ValueError('Message too long')
            self.send(message)
    
        def handle_read(self):
            message = self.recv(MAX_MESSAGE_LENGTH)
            self.log.info('Received message: %s', message)
    
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.INFO)
        logging.info('Creating host')
        host = Host()
        logging.info('Creating clients')
        alice = Client(host.getsockname(), 'Alice')
        bob = Client(host.getsockname(), 'Bob')
        alice.say('Hello, everybody!')
        logging.info('Looping')
        asyncore.loop()
    

    Which results in the following output:

    INFO:root:Creating host
    INFO:root:Creating clients
    INFO:Client (  Alice):Connecting to host at ('127.0.0.1', 51117)
    INFO:Client (    Bob):Connecting to host at ('127.0.0.1', 51117)
    INFO:Client (  Alice):Enqueued message: Hello, everybody!
    INFO:root:Looping
    INFO:Host:Accepted client at ('127.0.0.1', 55628)
    INFO:Host:Accepted client at ('127.0.0.1', 55629)
    INFO:Host:Broadcasting message: Hello, everybody!
    INFO:Client (  Alice):Received message: Hello, everybody!
    INFO:Client (    Bob):Received message: Hello, everybody!
    
    0 讨论(0)
  • 2020-12-03 09:15

    why use SocketServer? a simple client doesn't meet your needs?

    import socket
    
    HOST = ''
    PORT = 8000
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind((HOST, PORT))
    sock.listen(5)
    while True:
        conn, addr = sock.accept()
        print 'connecting to', addr
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.send(data)
    
    0 讨论(0)
  • 2020-12-03 09:18

    You can use socketserver to broadcast messages to all connected clients. However, the ability is not built into the code and will need to be implemented by extending some of the classes already provided. In the following example, this is implemented using the ThreadingTCPServer and StreamRequestHandler classes. They provide a foundation on which to build but still require some modifications to allow what you are trying to accomplish. The documentation should help explain what each function, class, and method are trying to do in order to get the job done.

    Server

    #! /usr/bin/env python3
    import argparse
    import pickle
    import queue
    import select
    import socket
    import socketserver
    
    
    def main():
        """Start a chat server and serve clients forever."""
        parser = argparse.ArgumentParser(description='Execute a chat server demo.')
        parser.add_argument('port', type=int, help='location where server listens')
        arguments = parser.parse_args()
        server_address = socket.gethostbyname(socket.gethostname()), arguments.port
        server = CustomServer(server_address, CustomHandler)
        server.serve_forever()
    
    
    class CustomServer(socketserver.ThreadingTCPServer):
    
        """Provide server support for the management of connected clients."""
    
        def __init__(self, server_address, request_handler_class):
            """Initialize the server and keep a set of registered clients."""
            super().__init__(server_address, request_handler_class, True)
            self.clients = set()
    
        def add_client(self, client):
            """Register a client with the internal store of clients."""
            self.clients.add(client)
    
        def broadcast(self, source, data):
            """Resend data to all clients except for the data's source."""
            for client in tuple(self.clients):
                if client is not source:
                    client.schedule((source.name, data))
    
        def remove_client(self, client):
            """Take a client off the register to disable broadcasts to it."""
            self.clients.remove(client)
    
    
    class CustomHandler(socketserver.StreamRequestHandler):
    
        """Allow forwarding of data to all other registered clients."""
    
        def __init__(self, request, client_address, server):
            """Initialize the handler with a store for future date streams."""
            self.buffer = queue.Queue()
            super().__init__(request, client_address, server)
    
        def setup(self):
            """Register self with the clients the server has available."""
            super().setup()
            self.server.add_client(self)
    
        def handle(self):
            """Run a continuous message pump to broadcast all client data."""
            try:
                while True:
                    self.empty_buffers()
            except (ConnectionResetError, EOFError):
                pass
    
        def empty_buffers(self):
            """Transfer data to other clients and write out all waiting data."""
            if self.readable:
                self.server.broadcast(self, pickle.load(self.rfile))
            while not self.buffer.empty():
                pickle.dump(self.buffer.get_nowait(), self.wfile)
    
        @property
        def readable(self):
            """Check if the client's connection can be read without blocking."""
            return self.connection in select.select(
                (self.connection,), (), (), 0.1)[0]
    
        @property
        def name(self):
            """Get the client's address to which the server is connected."""
            return self.connection.getpeername()
    
        def schedule(self, data):
            """Arrange for a data packet to be transmitted to the client."""
            self.buffer.put_nowait(data)
    
        def finish(self):
            """Remove the client's registration from the server before closing."""
            self.server.remove_client(self)
            super().finish()
    
    
    if __name__ == '__main__':
        main()
    

    Of course, you also need a client that can communicate with your server and use the same protocol the server speaks. Since this is Python, the decision was made to utilize the pickle module to facilitate data transfer among server and clients. Other data transfer methods could have been used (such as JSON, XML, et cetera), but being able to pickle and unpickle data serves the needs of this program well enough. Documentation is included yet again, so it should not be too difficult to figure out what is going on. Note that server commands can interrupt user data entry.

    Client

    #! /usr/bin/env python3
    import argparse
    import cmd
    import pickle
    import socket
    import threading
    
    
    def main():
        """Connect a chat client to a server and process incoming commands."""
        parser = argparse.ArgumentParser(description='Execute a chat client demo.')
        parser.add_argument('host', type=str, help='name of server on the network')
        parser.add_argument('port', type=int, help='location where server listens')
        arguments = parser.parse_args()
        client = User(socket.create_connection((arguments.host, arguments.port)))
        client.start()
    
    
    class User(cmd.Cmd, threading.Thread):
    
        """Provide a command interface for internal and external instructions."""
    
        prompt = '>>> '
    
        def __init__(self, connection):
            """Initialize the user interface for communicating with the server."""
            cmd.Cmd.__init__(self)
            threading.Thread.__init__(self)
            self.connection = connection
            self.reader = connection.makefile('rb', -1)
            self.writer = connection.makefile('wb', 0)
            self.handlers = dict(print=print, ping=self.ping)
    
        def start(self):
            """Begin execution of processor thread and user command loop."""
            super().start()
            super().cmdloop()
            self.cleanup()
    
        def cleanup(self):
            """Close the connection and wait for the thread to terminate."""
            self.writer.flush()
            self.connection.shutdown(socket.SHUT_RDWR)
            self.connection.close()
            self.join()
    
        def run(self):
            """Execute an automated message pump for client communications."""
            try:
                while True:
                    self.handle_server_command()
            except (BrokenPipeError, ConnectionResetError):
                pass
    
        def handle_server_command(self):
            """Get an instruction from the server and execute it."""
            source, (function, args, kwargs) = pickle.load(self.reader)
            print('Host: {} Port: {}'.format(*source))
            self.handlers[function](*args, **kwargs)
    
        def preloop(self):
            """Announce to other clients that we are connecting."""
            self.call('print', socket.gethostname(), 'just entered.')
    
        def call(self, function, *args, **kwargs):
            """Arrange for a handler to be executed on all other clients."""
            assert function in self.handlers, 'You must create a handler first!'
            pickle.dump((function, args, kwargs), self.writer)
    
        def do_say(self, arg):
            """Causes a message to appear to all other clients."""
            self.call('print', arg)
    
        def do_ping(self, arg):
            """Ask all clients to report their presence here."""
            self.call('ping')
    
        def ping(self):
            """Broadcast to all other clients that we are present."""
            self.call('print', socket.gethostname(), 'is here.')
    
        def do_exit(self, arg):
            """Disconnect from the server and close the client."""
            return True
    
        def postloop(self):
            """Make an announcement to other clients that we are leaving."""
            self.call('print', socket.gethostname(), 'just exited.')
    
    
    if __name__ == '__main__':
        main()
    
    0 讨论(0)
提交回复
热议问题