web-dev-qa-db-fra.com

Socket Python recevoir - les paquets entrants ont toujours une taille différente

J'utilise le module SocketServer pour un serveur TCP . Je rencontre un problème avec la fonction recv(), car les paquets entrants ont toujours une taille différente, donc si je spécifie recv(1024) (j'ai essayé avec une valeur plus grande et plus petite), il se bloque après 2 ou 3 requêtes car la longueur du paquet sera plus petite (je pense), puis le serveur reste bloqué jusqu'à l'expiration du délai imparti.

class Test(SocketServer.BaseRequestHandler):

def handle(self):

   print "From:", self.client_address

   while True:    

     data = self.request.recv(1024)
     if not data: break

     if data[4] == "\x20":              
       self.request.sendall("hello")
     if data[4] == "\x21":
       self.request.sendall("bye")
     else:
       print "unknow packet"
   self.request.close()
   print "Disconnected", self.client_address

launch = SocketServer.ThreadingTCPServer(('', int(sys.argv[1])),Test)

launch.allow_reuse_address= True;

launch.serve_forever()

Si le client envoie plusieurs requêtes sur le même port source, mais que le serveur reste bloqué, toute aide serait la bienvenue, merci! 

29
n00bie

La réponse de Larry Hastings donne d'excellents conseils d'ordre général sur les sockets, mais il existe quelques erreurs en ce qui concerne le fonctionnement de la méthode recv(bufsize) dans le module socket Python.

Alors, pour clarifier, car cela peut être déroutant pour les autres qui cherchent de l'aide:

  1. Le paramètre bufsize pour la méthode recv(bufsize) n'est pas facultatif. Vous obtiendrez une erreur si vous appelez recv() (sans le paramètre).
  2. Le bufferlen dans recv(bufsize) est une taille de maximum. Le recv retournera volontiers moins d'octets s'il y en a moins disponible.

Voir la documentation pour plus de détails.

Maintenant, si vous recevez des données d'un client et que vous voulez savoir quand vous les avez toutes reçues, vous devrez probablement les ajouter à votre protocole - comme le suggère Larry. Voir cette recette pour les stratégies permettant de déterminer la fin du message.

Comme le souligne cette recette, pour certains protocoles, le client se déconnectera simplement une fois l'envoi des données terminé. Dans ces cas, votre boucle while True devrait fonctionner correctement. Si le client not ne se déconnecte pas, vous devez trouver un moyen de signaler la longueur de votre contenu, de délimiter vos messages ou d'implémenter un délai d'attente.

Je serais heureux d'essayer de vous aider davantage si vous pouviez publier votre code client exact et une description de votre protocole de test.

120
Hans L

Vous pouvez également utiliser recv(x_bytes, socket.MSG_WAITALL), qui semble ne fonctionner que sous Unix, et renverra exactement x_bytes.

14
henrietta

C’est la nature de TCP: le protocole remplit les paquets (la couche inférieure étant des paquets IP) et les envoie. Vous pouvez avoir un certain degré de contrôle sur le MTU (Maximum Transfer Unit).

En d'autres termes: vous devez concevoir un protocole qui se superpose à TCP où votre "délimitation de la charge utile" est définie. Par "délimitation de la charge utile", j'entends la façon dont vous extrayez l'unité de message que votre protocole prend en charge. Cela peut être aussi simple que "chaque chaîne terminée par un caractère NULL".

2
jldupont

Notez que la raison exacte pour laquelle votre code est gelé est pas car vous définissez une taille de mémoire tampon request.recv () trop élevée. Ici est expliqué Que signifie taille du tampon dans socket.recv (buffer_size)

Ce code fonctionnera jusqu'à ce qu'il reçoive un message empty TCP (si vous imprimez ce message vide, il affichera b''):

while True:    
  data = self.request.recv(1024)
  if not data: break

Et notez qu'il n'y a que aucun moyen d'envoyer un message vide TCP. socket.send(b'') ne fonctionnera tout simplement pas. 

Pourquoi? Comme un message vide est envoyé uniquement lorsque vous tapez socket.close(), votre script sera mis en boucle tant que vous ne fermerez pas votre connexion. Comme Hans L a souligné voici quelques de bonnes méthodes pour mettre fin au message .

1
Qback

Je sais que c'est vieux, mais j'espère que cela aidera quelqu'un. 

En utilisant des sockets python classiques, j'ai découvert que vous pouvez envoyer et recevoir des informations par paquets en utilisant sendto et recvfrom

# tcp_echo_server.py
import socket

ADDRESS = ''
PORT = 54321

connections = []
Host = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Host.setblocking(0)
Host.bind((ADDRESS, PORT))
Host.listen(10)  # 10 is how many clients it accepts

def close_socket(connection):
    try:
        connection.shutdown(socket.SHUT_RDWR)
    except:
        pass
    try:
        connection.close()
    except:
        pass

def read():
    for i in reversed(range(len(connections))):
        try:
            data, sender = connections[i][0].recvfrom(1500)
            return data
        except (BlockingIOError, socket.timeout, OSError):
            pass
        except (ConnectionResetError, ConnectionAbortedError):
            close_socket(connections[i][0])
            connections.pop(i)
    return b''  # return empty if no data found

def write(data):
    for i in reversed(range(len(connections))):
        try:
            connections[i][0].sendto(data, connections[i][1])
        except (BlockingIOError, socket.timeout, OSError):
            pass
        except (ConnectionResetError, ConnectionAbortedError):
            close_socket(connections[i][0])
            connections.pop(i)

# Run the main loop
while True:
    try:
        con, addr = Host.accept()
        connections.append((con, addr))
    except BlockingIOError:
        pass

    data = read()
    if data != b'':
        print(data)
        write(b'ECHO: ' + data)
        if data == b"exit":
            break

# Close the sockets
for i in reversed(range(len(connections))):
    close_socket(connections[i][0])
    connections.pop(i)
close_socket(Host)

Le client est similaire

# tcp_client.py
import socket

ADDRESS = "localhost"
PORT = 54321

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((ADDRESS, PORT))
s.setblocking(0)

def close_socket(connection):
    try:
        connection.shutdown(socket.SHUT_RDWR)
    except:
        pass
    try:
        connection.close()
    except:
        pass

def read():
    """Read data and return the read bytes."""
    try:
        data, sender = s.recvfrom(1500)
        return data
    except (BlockingIOError, socket.timeout, AttributeError, OSError):
        return b''
    except (ConnectionResetError, ConnectionAbortedError, AttributeError):
        close_socket(s)
        return b''

def write(data):
    try:
        s.sendto(data, (ADDRESS, PORT))
    except (ConnectionResetError, ConnectionAbortedError):
        close_socket(s)

while True:
    msg = input("Enter a message: ")
    write(msg.encode('utf-8'))

    data = read()
    if data != b"":
        print("Message Received:", data)

    if msg == "exit":
        break

close_socket(s)
0
justengel