Python socket receive - les paquets entrants ont toujours une taille différente

j'utilise le module SocketServer pour un serveur TCP. Je suis confronté à un problème ici avec la fonction recv() , parce que les paquets entrants ont toujours une taille différente, donc si je spécifie recv(1024) (j'ai essayé avec une plus grande valeur, et plus petit), il est coincé après 2 ou 3 requêtes parce que la longueur du paquet sera plus petit (je pense), et puis le serveur est coincé jusqu'à un délai.

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 la même port source, mais le serveur est bloqué, toute aide serait très apprécié, merci !

27
demandé sur Paul Rooney 2009-11-10 18:34:11

6 réponses

le réseau est toujours imprévisible. TCP fait que beaucoup de ces comportements aléatoires disparaissent pour vous. Une chose merveilleuse que TCP fait: il garantit que les octets arriveront dans le même ordre. Mais! Il ne pas garantie qu'ils arriveront découpés de la même manière. Vous simplement ne peut pas supposer que chaque envoi() à partir d'une extrémité de la connexion se traduira par exactement un recv () à l'extrémité opposée avec exactement le même nombre d'octets.

quand vous dites socket.recv(x) , vous dites "ne revenez pas tant que vous n'avez pas lu x octets à partir de la socket". Cela s'appelle "bloquer l'entrée/sortie": vous bloquerez (attendez) jusqu'à ce que votre demande ait été remplie. Si chaque message dans votre protocole était exactement 1024 octets, appeler socket.recv(1024) fonctionnerait très bien. Mais il semble que ce n'est pas vrai. Si vos messages sont un nombre fixe d'octets, il suffit de passer ce nombre à socket.recv() et vous avez terminé.

mais que faire si vos messages peuvent être de différentes longueurs? La première chose que vous devez faire: arrêter d'appeler socket.recv() avec un nombre explicite. Changer ceci:

data = self.request.recv(1024)

à ceci:

data = self.request.recv()

signifie recv() sera toujours de retour chaque fois qu'il obtient de nouvelles données.

Mais vous avez maintenant un nouveau problème: comment savez-vous si l'expéditeur a envoyé un message complet? La réponse est: vous ne le fais pas. Vous allez devoir faire de la longueur du message Une partie explicite de votre protocole. Voici la meilleure façon: préfixez chaque message avec une longueur, soit comme un entier de taille fixe (converti en byte d'ordre de réseau en utilisant socket.ntohs() ou socket.ntohl() s'il vous plaît!) ou sous forme de chaîne suivie d'un délimiteur (comme '123:'). Cette seconde approche est souvent moins efficace, mais elle est plus facile en Python.

une fois que vous avez ajouté cela à votre protocole, vous devez changer votre code en gérer recv() retourner des quantités arbitraires de données à tout moment. Voici un exemple de comment faire cela. J'ai essayé de l'écrire en pseudo-code, ou avec des commentaires pour vous dire quoi faire, mais il n'était pas très clair. Donc je l'ai écrit explicitement en utilisant le préfixe de longueur comme une chaîne de chiffres terminée par deux points. Et voilà:

length = None
buffer = ""
while True:
  data += self.request.recv()
  if not data:
    break
  buffer += data
  while True:
    if length is None:
      if ':' not in buffer:
        break
      # remove the length bytes from the front of buffer
      # leave any remaining bytes in the buffer!
      length_str, ignored, buffer = buffer.partition(':')
      length = int(length_str)

    if len(buffer) < length:
      break
    # split off the full message from the remaining bytes
    # leave any remaining bytes in the buffer!
    message = buffer[:length]
    buffer = buffer[length:]
    length = None
    # PROCESS MESSAGE HERE
35
répondu Larry Hastings 2009-11-11 16:02:59

la réponse de Larry Hastings contient quelques bons conseils généraux sur les sockets, mais il y a quelques erreurs en ce qui concerne la façon dont la méthode recv(bufsize) fonctionne dans le module de socket Python.

donc, pour clarifier, puisque cela peut être déroutant pour d'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).
  2. le bufferlen dans recv(bufsize) est un maximum taille. Le recv renverra volontiers moins d'octets s'il y en a moins disponibles.

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 avez reçu toutes les données, vous allez probablement devoir les ajouter à votre protocole -- comme Larry le suggère. Voir ce recette pour les stratégies de détermination de la fin de message.

comme le souligne cette recette, pour certains protocoles, le client se déconnectera simplement quand il aura fini d'envoyer des données. Dans ces cas, votre boucle while True devrait fonctionner correctement. Si le client ne pas déconnecter, vous aurez besoin de comprendre un moyen de signaler votre longueur de contenu, délimiter vos messages, ou mettre en œuvre un timeout.

je serais heureux d'essayer d'aider de plus, si vous pouviez afficher votre code client exact et une description de votre protocole de test.

118
répondu Hans L 2009-11-27 05:43:38

vous pouvez alternativement utiliser recv(x_bytes, socket.MSG_WAITALL) , qui semble fonctionner seulement sur Unix, et retournera exactement x_bytes .

14
répondu henrietta 2017-08-07 14:12:17

C'est la nature de TCP: le protocole remplit des paquets (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 chevauche TCP où votre" délimitation de charge utile " est définie. Par "délimitation de la charge utile", je veux dire la façon dont vous extrayez l'Unité de message que votre protocole supporte. Cela peut être aussi simple que "toutes les chaînes à terminaison nulle".

2
répondu jldupont 2009-11-10 15:38:33

notez que raison exacte pourquoi votre code est gelé est et non parce que vous avez placé une requête trop élevée.Taille du tampon recv (). Ici est expliqué ce qui signifie la taille du tampon dans la socket.recv (buffer_size)

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

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

et note, qu'il y a no way pour envoyer un message TCP vide. socket.send(b'') ne marchera pas.

pourquoi? Parce que le message vide est envoyé seulement quand vous tapez socket.close() , donc votre script Boucle aussi longtemps que vous ne fermez pas votre connexion. Comme Hans l fait remarquer ici quelques bonnes méthodes pour terminer le message .

1
répondu Qback 2017-12-15 11:46:23

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

en utilisant des sockets python réguliers j'ai trouvé que vous pouvez envoyer et recevoir des informations dans des 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
répondu justengel 2017-04-26 18:30:50