Python SocketServer:发送到多个客户端?

发布于 2024-09-18 10:32:39 字数 1842 浏览 14 评论 0原文

好吧,我正在尝试使用 SocketServer 构建一个小型 python 程序,该程序应该将其收到的消息发送给所有连接的客户端。我陷入困境,我不知道如何在服务器端存储客户端,也不知道如何发送到多个客户端。哦,每当有超过 1 个客户端连接时,每次客户端发送超过一条消息时,我的程序都会失败...

到目前为止,这是我的代码:

        print str(self.client_address[0])+' connected.'
    def handle(self):
        new=1
        for client in clients:
            if client==self.request:
                new=0
        if new==1:
            clients.append(self.request)
        for client in clients:
            data=self.request.recv(1024)
            client.send(data)

class Host:
    def __init__(self):
        self.address = ('localhost', 0)
        self.server = SocketServer.TCPServer(self.address, EchoRequestHandler)
        ip, port = self.server.server_address
        self.t = threading.Thread(target=self.server.serve_forever)
        self.t.setDaemon(True)
        self.t.start()
        print ''
        print 'Hosted with IP: '+ip+' and port: '+str(port)+'. Clients can now connect.'
        print ''
    def close(self):
        self.server.socket.close()

class Client:
    name=''
    ip=''
    port=0
    def __init__(self,ip,port,name):
        self.name=name
        self.hostIp=ip
        self.hostPort=port
        self.s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((self.hostIp, self.hostPort))
    def reco(self):
        self.s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((self.hostIp, self.hostPort))
    def nick(self,newName):
        self.name=newName
    def send(self,message):
        message=self.name+' : '+message
        len_sent=self.s.send(message)
        response=self.s.recv(len_sent)
        print response
        self.reco()
    def close(self):
        self.s.close()

显然我不知道我在做什么,所以任何帮助都会很棒。< br> 提前致谢!

编辑:我在 Windows Vista 上使用 Python 2.7。

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 store clients on the serverside, and I don't know how to send to multiple clients. Oh and, my program fails everytime more then 1 client connects, and everytime a client sends more then one message...

Here's my code until now:

        print str(self.client_address[0])+' connected.'
    def handle(self):
        new=1
        for client in clients:
            if client==self.request:
                new=0
        if new==1:
            clients.append(self.request)
        for client in clients:
            data=self.request.recv(1024)
            client.send(data)

class Host:
    def __init__(self):
        self.address = ('localhost', 0)
        self.server = SocketServer.TCPServer(self.address, EchoRequestHandler)
        ip, port = self.server.server_address
        self.t = threading.Thread(target=self.server.serve_forever)
        self.t.setDaemon(True)
        self.t.start()
        print ''
        print 'Hosted with IP: '+ip+' and port: '+str(port)+'. Clients can now connect.'
        print ''
    def close(self):
        self.server.socket.close()

class Client:
    name=''
    ip=''
    port=0
    def __init__(self,ip,port,name):
        self.name=name
        self.hostIp=ip
        self.hostPort=port
        self.s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((self.hostIp, self.hostPort))
    def reco(self):
        self.s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.s.connect((self.hostIp, self.hostPort))
    def nick(self,newName):
        self.name=newName
    def send(self,message):
        message=self.name+' : '+message
        len_sent=self.s.send(message)
        response=self.s.recv(len_sent)
        print response
        self.reco()
    def close(self):
        self.s.close()

Obviously I have no idea what I'm doing, so any help would be great.
Thanks in advance!

Edit: I'm using Python 2.7 on Windows Vista.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

粉红×色少女 2024-09-25 10:32:39

您想在此处查看 asyncore 。您在客户端调用的套接字操作是阻塞的(直到收到一些数据或发生超时才返回),这使得很难侦听从主机发送的消息并让客户端实例将数据排入队列以发送同时。 asyncore 应该将基于超时的轮询循环从您手中抽象出来。

这是一个代码“示例”——如果有任何不清楚的地方请告诉我:

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()

这会产生以下输出:

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!

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!
近箐 2024-09-25 10:32:39

您可以使用 socketserver 将消息广播到所有连接的客户。但是,该功能并未内置到代码中,需要通过扩展一些已提供的类来实现。在以下示例中,这是使用 ThreadingTCPServer 和 StreamRequestHandler 类实现的。它们提供了构建的基础,但仍然需要进行一些修改才能实现您想要完成的任务。文档应该帮助解释每个函数、类和方法为了完成工作而试图做什么。

服务器

#! /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()

当然,您还需要一个可以与服务器通信并使用与服务器相同的协议的客户端。由于这是 Python,因此决定使用 pickle< /a> 模块以促进服务器和客户端之间的数据传输。可以使用其他数据传输方法(例如 JSON、XML 等),但能够 pickle 和 unpickle 数据足以满足该程序的需求。文档再次包含在内,因此弄清楚发生了什么应该不会太困难。请注意,服务器命令可能会中断用户数据输入。

客户端

#! /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()

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()
皇甫轩 2024-09-25 10:32:39

为什么使用SocketServer?简单的客户端无法满足您的需求?

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)

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)
不寐倦长更 2024-09-25 10:32:39

要同时获取多个客户端,您必须添加 SocketServer.ForkingMixIn 或 ThreadingMixIn 。

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

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文