2010-06-15 4 views
1

J'ai un serveur tcp qui utilise l'appel select pour multiplexer la lecture des clients. J'ai une classe client (MClient) qui gère le décodage des paquets de données entrantsPython TCP Server, écrire aux clients?

while(1) 

    rlist, wlist, xlist = select(input_sockets, output_sockets, [] , 1) 

    for insock in rlist: #any clients???? 

     if insock is server_socket: 
      new_socket, addr = server_socket.accept() 
      input_sockets.append(new_socket)  
      clients[insock.fileno()] = MClient(new_socket, addr, client_id) #dict of clients   
     else: 
      data = insock.recv(512) 
      if data: 
       clients[insock.fileno()].ProcessPacket(data) 
      else: 
       input_sockets.remove(insock) 
       del clients[insock.fileno()] 

    #handle writing to sockets 
    for outsock in wlist: 
     ....not done yet 



    #do some other stuff not associated with the socket 

Je suis confus quant à la façon de traiter l'envoi de données au client, i.e. comment écrire à la liste « output_sockets ». Je pense à placer un drapeau dans mon objet MClient qui indique que j'ai des données à renvoyer au client. Je voudrais alors dans ma boucle de serveur vérifier chacun des clients pour voir si ce drapeau a été mis, puis volate la liste de sortie avec le socket correspondant. Lorsque le socket est disponible pour l'écriture, j'appellerais alors la fonction d'écriture des clients appropriés.

Ce schéma ne semble pas très élégant, je voudrais gérer l'écriture dans la boucle du serveur principal. Comment pourrais-je accomplir cela?

Merci

+0

Cela pourrait être légèrement hors de portée de la question, mais avez-vous eu un coup d'œil à asyncore? C'est un module Python standard pour gérer les serveurs comme celui que vous avez. – Krumelur

+0

Merci Krumelur, j'ai regardé cela, mais je dois gérer d'autres choses dans ma boucle de serveur principal et je n'ai pas vu une façon de le faire avec asyncore – mikip

+0

indice - regardez les paramètres 'count' et 'timeout' que vous pouvez passer à asyncore.loop. – Kylotan

Répondre

0

La mise en œuvre que vous décrivez dans votre premier paragraphe sonne comme la façon traditionnelle de la mise en œuvre d'un serveur à l'aide select.

Si vous voulez être en mesure d'écrire au client dans la « boucle principale du serveur », par lequel je suppose que vous voulez dire avoir du code qui ressemble à:

request = socket.recv() 
response = process_request(request) 
socket.send(response) 

alors vous aurez besoin d'avoir un fil séparé par client.

+0

merci jchl, je ne voulais pas le faire multithread, mon bref était de le garder en un seul thread – mikip

1

Voici quelque chose que j'ai écrit il y a quelque temps pour en savoir plus sur le traitement de plusieurs connexions avec un seul thread. Ce n'est pas parfait mais illustre ce que vous voulez faire. L'objet client gère les flux de lecture et d'écriture de la connexion et s'assure que le serveur dispose du socket client dans les listes select() appropriées. Cela implémente un protocole simple où les messages sont terminés par des retours à la ligne. Les fonctions pumpXXXX() bloquent simplement la lecture/écriture des flux et la gestion des tampons de lecture/écriture. Les messages complets sont traités uniquement lorsque des retours à la ligne sont trouvés dans les tampons.

import socket 
import select 

class Client(object): 

    '''This object is created for each client connection. It tracks 
    what has been read, what has been written, and processes complete 
    messages terminated by newlines. It responds by returning the 
    original message wrapped in square brackets and terminated by a 
    newline. ''' 

    def __init__(self,who,sock,server): 

     '''who - client address 
     sock - client socket 
     server - server object for this client 
     ''' 

     self.who = who 
     self.readbuf = '' 
     self.writbuf = '' 
     self.server = server 
     self.sock = sock 

    def close(self): 

     '''Removes client from server's reader/writer queues and 
     closes the connection.''' 

     self.sock.close() 
     if self.sock in self.server.readers: 
      self.server.readers.remove(self.sock) 
     if self.sock in self.server.writers: 
      self.server.writers.remove(self.sock) 
     self.server.data.pop(self.sock) 

    def pumprecv(self): 

     '''Server calls pumprecv() when something is readable from the 
     client socket. The data is appended to the client's read 
     buffer.mro Complete messages (if any) are then removed from 
     the buffer and processed.''' 

     try: 
      tmp = self.sock.recv(1000) 
     except socket.error,e: 
      print 'recv',e 
      self.close() 
     else:     
      if tmp: 
       self.readbuf += tmp 

       # Complete messages are processed 
       while '\n' in self.readbuf: 
        msg,self.readbuf = self.readbuf.split('\n',1) 
        print self.who,msg 
        self.writbuf += '[' + msg + ']\n' 
        # New data to send. Make sure client is in the 
        # server's writer queue. 
        if self.sock not in self.server.writers: 
         self.server.writers.append(self.sock) 
      else: 
       self.close() 

    def pumpsend(self): 
     try: 
      # send some data. tmp is #chars sent (may not be all in writbuf). 
      tmp = self.sock.send(self.writbuf) 
     except socket.error,e: 
      print 'send:',e 
      self.close() 
     else: 
      # Removed sent characters from writbuf. 
      self.writbuf = self.writbuf[tmp:] 
      # If writbuf is empty, remove socket from server's write queue. 
      if not self.writbuf: 
       self.server.writers.remove(self.sock) 

class Server(object): 
    def __init__(self,ip='127.0.0.1',port=9999): 
     self.ssock = socket.socket() 
     self.ssock.bind((ip,port)) 
     self.ssock.listen(5) 
     self.readers = [self.ssock] 
     self.data = {} 
     self.writers = [] 
     self.quit = False 

    def pumpaccept(self): 

     '''Called when server socket is readable to accept a 
     connection and create a Client object.''' 

     csock,who = self.ssock.accept() 
     print 'Connected %s:%d' % who 
     self.readers.append(csock) 
     self.data[csock] = Client(who,csock,self) 

    def serve(self): 
     while not self.quit or self.writers: 
      readable,writable,other = select.select(self.readers,self.writers,[],1.0) 
      # Operate on copies of the queues since the pumpXXX() commands can modify the lists. 
      if self.ssock in readable[:]: 
       self.pumpaccept() 
       readable.remove(self.ssock) 
      for reader in readable[:]: 
       self.data[reader].pumprecv() 
      for writer in writable[:]: 
       self.data[writer].pumpsend() 

      if not readable and not writable and not other: 
       print '.', 

if __name__ == '__main__': 
    srv = Server() 
    srv.serve() 

J'ai testé cela en lançant le serveur dans une console, et en exécutant le code suivant dans d'autres consoles pour tester les connexions multiples. Effectuez plusieurs connexions, échangez des envois à partir de différentes fenêtres et envoyez des messages partiels pour voir comment le serveur répond.

>>> from socket import * 
>>> s=socket() 
>>> s.connect(('localhost',9999)) 
>>> s.send('one\ntwo\nthree') 
13 
>>> s.send('\nfour\n') 
6 
>>> s.recv(1024) 
'[one]\n[two\three]\n[four]\n' 
>>> s.close() 

sortie devrait ressembler à:

. . . . . . . . . . . . . . . . . . . Connected 127.0.0.1:1514 
. . . . . . . . . ('127.0.0.1', 1514) one 
. . . . . . . ('127.0.0.1', 1514) two 
. . . ('127.0.0.1', 1514) three 
('127.0.0.1', 1514) four 
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 
Questions connexes