2010-09-08 12 views
9

Beh, sto provando a costruire un piccolo prgram python con un SocketServer che dovrebbe inviare messaggi che riceve a tutti i client connessi. Sono bloccato, non so come memorizzare i client sul lato server e non so come inviare a più client. Oh, e, il mio programma non riesce ogni volta più di 1 client si connette, e ogni volta che un client invia più di un messaggio ...Python SocketServer: invio a più client?

Ecco il mio codice fino ad ora:

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

Ovviamente non ho idea di quello che ho' sto facendo, quindi qualsiasi aiuto sarebbe grande.
Grazie in anticipo!

Modifica: sto usando Python 2.7 su Windows Vista.

+0

Ho avuto un problema simile risolto qui: [server/client code] (http://stackoverflow.com/questions/41785969/python-tcp-server-accepting-connections -and-broadcasting-commands/41786133 # 41786133) –

risposta

15

Qui si vuole guardare asyncore qui. Le operazioni di socket che stai chiamando sul lato client stanno bloccando (non tornare finché non si ricevono dati o si verifica un timeout) che rende difficile ascoltare i messaggi inviati dall'host e lasciare che le istanze client accodano i dati da inviare a lo stesso tempo. asyncore dovrebbe estrarre il loop di polling basato sul timeout lontano da te.

Ecco un codice di "campione" - fatemi sapere se qualcosa non è chiaro:

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

che si traduce nella seguente 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

Grazie, sembra quello che stavo cercando! Purtroppo, non sono riuscito a farlo funzionare all'esterno __main__: ho aggiunto asyncore.loop() alla fine di Host .__ init __(), e il mio oggetto host accetta le connessioni client, ma non reagisce ai messaggi inviati .. – Alex

+0

@Alex: asyncore.loop() funziona per sempre! Effettivamente chiamandolo stai dicendo, "Ho finito di controllare il programma, passare le cose al ciclo asyncore in modo che possa gestire l'invio/ricezione per il resto del tempo." Si noti come ho impostato tutto prima di chiamare asyncore.loop(). Cosa stai cercando di fare spostandolo? – cdleary

+0

Bene, non voglio eseguire solo alcune connessioni definite, ma avere l'host in esecuzione e i client che sono in grado di connettersi/inviare messaggi in qualsiasi momento. Grazie per il tuo tempo! – Alex

0

perché usare SocketServer? un semplice cliente non soddisfa le tue esigenze?

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) 
+6

non impedirà il collegamento di altri client? – Codler

+0

Questo non funzionerà. –

0

Per prendere più client contemporaneamente, si dovrà aggiungere SocketServer.ForkingMixIn o ThreadingMixIn.

5

È possibile utilizzare socketserver per trasmettere messaggi a tutti i client connessi. Tuttavia, l'abilità non è incorporata nel codice e dovrà essere implementata estendendo alcune delle classi già fornite. Nell'esempio seguente, questo viene implementato utilizzando le classi ThreadingTCPServer e StreamRequestHandler. Forniscono una base su cui costruire, ma richiedono ancora alcune modifiche per consentire ciò che stai cercando di realizzare. La documentazione dovrebbe aiutare a spiegare ciò che ogni funzione, classe e metodo stanno cercando di fare per portare a termine il lavoro.

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

Naturalmente, è necessario anche un client in grado di comunicare con il server e utilizzare lo stesso protocollo del server parla. Poiché questo è Python, è stata presa la decisione di utilizzare il modulo pickle per facilitare il trasferimento dei dati tra server e client. Altri metodi di trasferimento dei dati potrebbero essere stati utilizzati (come JSON, XML, eccetera), ma essere in grado di mettere sottosopra e desumere i dati soddisfa abbastanza bene le esigenze di questo programma. La documentazione è inclusa di nuovo, quindi non dovrebbe essere troppo difficile capire cosa sta succedendo. Si noti che i comandi del server possono interrompere l'immissione dei dati dell'utente.

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