web-dev-qa-db-fra.com

Le socket Python reçoit une grande quantité de données

Lorsque j'essaie de recevoir de plus grandes quantités de données, elles sont coupées et je dois appuyer sur Entrée pour obtenir le reste des données. Au début, je pouvais l'augmenter un peu, mais il ne recevrait toujours pas tout. Comme vous pouvez le voir, j'ai augmenté la mémoire tampon sur la conn.recv (), mais toutes les données ne sont toujours pas reçues. Cela le coupe à un moment donné. Je dois appuyer sur Entrée sur mon raw_input afin de recevoir le reste des données. Est-ce que je peux quand même obtenir toutes les données en même temps? Voici le code. 

port = 7777
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', port))
sock.listen(1)
print ("Listening on port: "+str(port))
while 1:
    conn, sock_addr = sock.accept()
    print "accepted connection from", sock_addr
    while 1:
        command = raw_input('Shell> ')
            conn.send(command)
                data = conn.recv(8000)
                if not data: break
                print data,
    conn.close()
35
user2585107

TCP/IP est un protocole basé sur le flux, pas un protocole basé sur un message Rien ne garantit que chaque appel send() par un homologue entraîne un seul appel recv() par l'autre homologue recevant les données exactes envoyées. Il pourrait recevoir les données fragmentées, réparties en plusieurs appels recv(), en raison de la fragmentation des paquets.

Vous devez définir votre propre protocole basé sur des messages en plus de TCP afin de différencier les limites des messages. Ensuite, pour lire un message, vous continuez à appeler recv() jusqu'à ce que vous ayez lu l'intégralité du message ou qu'une erreur se produise.

Un moyen simple d’envoyer un message consiste à préfixer chaque message par sa longueur. Ensuite, pour lire un message, vous devez d'abord lire la longueur, puis plusieurs octets. Voici comment vous pouvez le faire:

def send_msg(sock, msg):
    # Prefix each message with a 4-byte length (network byte order)
    msg = struct.pack('>I', len(msg)) + msg
    sock.sendall(msg)

def recv_msg(sock):
    # Read message length and unpack it into an integer
    raw_msglen = recvall(sock, 4)
    if not raw_msglen:
        return None
    msglen = struct.unpack('>I', raw_msglen)[0]
    # Read the message data
    return recvall(sock, msglen)

def recvall(sock, n):
    # Helper function to recv n bytes or return None if EOF is hit
    data = b''
    while len(data) < n:
        packet = sock.recv(n - len(data))
        if not packet:
            return None
        data += packet
    return data

Ensuite, vous pouvez utiliser les fonctions send_msg et recv_msg pour envoyer et recevoir des messages entiers. Ils n'auront aucun problème avec la division ou la fusion des paquets au niveau du réseau.

97
Adam Rosenfield

Vous pouvez l'utiliser comme suit: data = recvall(sock)

def recvall(sock):
    BUFF_SIZE = 4096 # 4 KiB
    data = b''
    while True:
        part = sock.recv(BUFF_SIZE)
        data += part
        if len(part) < BUFF_SIZE:
            # either 0 or end of data
            break
    return data
17
JadedTuna

La réponse acceptée est acceptable mais elle sera très lente avec les gros fichiers. -String est une classe immuable. Cela signifie que plus d'objets sont créés chaque fois que vous utilisez le signe +. Utiliser list comme structure de pile sera plus efficace.

Cela devrait mieux fonctionner 

while True: 
    chunk = s.recv(10000)
    if not chunk: 
        break
    fragments.append(chunk)

print "".join(fragments)
5
Mina Gabriel

Vous devrez peut-être appeler conn.recv () plusieurs fois pour recevoir toutes les données. L'appel d'une seule fois n'est pas garanti pour importer toutes les données envoyées, car les flux TCP ne conservent pas les limites des trames (c'est-à-dire qu'ils fonctionnent uniquement en tant que flux d'octets bruts, et non structurés. flux de messages).

Voir cette réponse pour une autre description du problème.

Notez que cela signifie que vous avez besoin d’un moyen de savoir quand vous avez reçu toutes les données. Si l'expéditeur envoie toujours exactement 8 000 octets, vous pouvez compter le nombre d'octets que vous avez reçus jusqu'à présent et le soustraire de 8 000 pour savoir combien il en reste à recevoir. Si les données sont de taille variable, il existe diverses autres méthodes pouvant être utilisées, telles que l'envoi par l'en-tête d'un en-tête de nombre d'octets avant l'envoi du message ou le texte ASCII qui vous est envoyé. pourrait rechercher un caractère de nouvelle ligne ou NUL.

4
Jeremy Friesner

Une variante utilisant une fonction génératrice (que je considère plus pythonique):

def recvall(sock, buffer_size=4096):
    buf = sock.recv(buffer_size)
    while buf:
        yield buf
        if len(buf) < buffer_size: break
        buf = sock.recv(buffer_size)
# ...
with socket.create_connection((Host, port)) as sock:
    sock.sendall(command)
    response = b''.join(recvall(sock))
3
yoniLavi

Modifier le code d'Adam Rosenfield:

import sys


def send_msg(sock, msg):
    size_of_package = sys.getsizeof(msg)
    package = str(size_of_package)+":"+ msg #Create our package size,":",message
    sock.sendall(package)

def recv_msg(sock):
    try:
        header = sock.recv(2)#Magic, small number to begin with.
        while ":" not in header:
            header += sock.recv(2) #Keep looping, picking up two bytes each time

        size_of_package, separator, message_fragment = header.partition(":")
        message = sock.recv(int(size_of_package))
        full_message = message_fragment + message
        return full_message

    except OverflowError:
        return "OverflowError."
    except:
        print "Unexpected error:", sys.exc_info()[0]
        raise

J'encouragerais toutefois fortement l'utilisation de l'approche originale.

1
sjMoquin

Vous pouvez le faire en utilisant la sérialisation

from socket import *
from json import dumps, loads

def recvall(conn):
    data = ""
    while True:
    try:
        data = conn.recv(1024)
        return json.loads(data)
    except ValueError:
        continue

def sendall(conn):
    conn.sendall(json.dumps(data))

NOTE: Si vous voulez utiliser un fichier avec le code ci-dessus, vous devez l’encoder/le décoder en base64.

0
John Albert