web-dev-qa-db-fra.com

Quelle est la bonne façon d'envoyer une grande quantité de données sur des sockets en Python?

Récemment, j'ai écrit du code (client et serveur) pour envoyer une image - le client télécharge simplement l'image sur le serveur, en utilisant simplement le module socket: Envoi d'image via des sockets (UNIQUEMENT) en Python, l'image ne peut pas être ouverte .

Cependant, la partie d'envoi d'image est maintenant ce qui me préoccupe. Voici l'image originale que j'utilise:

enter image description here

Dans mon code serveur (qui reçoit les images), j'ai ces lignes:

myfile = open(basename % imgcounter, 'wb')
myfile.write(data)

data = sock.recv(40960000)
if not data:
     myfile.close()
     break
myfile.write(data)
myfile.close()

sock.sendall("GOT IMAGE")
sock.shutdown()

Mais je ne pense pas que ce soit la meilleure façon de procéder. Je pense que je devrais plutôt implémenter le serveur de telle sorte qu'il reçoive les données en morceaux:

#!/usr/bin/env python

import random
import socket, select
from time import gmtime, strftime
from random import randint

imgcounter = 1
basename = "image%s.png"

Host = '127.0.0.1'
PORT = 2905

connected_clients_sockets = []

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((Host, PORT))
server_socket.listen(10)

connected_clients_sockets.append(server_socket)

while True:

    read_sockets, write_sockets, error_sockets = select.select(connected_clients_sockets, [], [])

    for sock in read_sockets:

        if sock == server_socket:

            sockfd, client_address = server_socket.accept()
            connected_clients_sockets.append(sockfd)

        else:
            try:

                data = sock.recv(4096)
                txt = str(data)

                if data:

                    if data.startswith('SIZE'):
                        tmp = txt.split()
                        size = int(tmp[1])

                        print 'got size %s' % size

                        sock.sendall("GOT SIZE")

                    Elif data.startswith('BYE'):
                        sock.shutdown()

                    else :

                        myfile = open(basename % imgcounter, 'wb')
                        myfile.write(data)

                        amount_received = 0
                        while amount_received < size:
                            data = sock.recv(4096)
                            amount_received += len(data)

                            print amount_received

                            if not data:
                                break
                            myfile.write(data)
                        myfile.close()

                        sock.sendall("GOT IMAGE")
                        sock.shutdown()
            except:
                sock.close()
                connected_clients_sockets.remove(sock)
                continue
        imgcounter += 1
server_socket.close()

Mais quand je fais cela, le serveur imprime:

got size 54674
4096
8192
12288
16384
20480
24576
28672
32768
36864
40960
45056
49152
50578

Et puis semble se bloquer, et le client se bloque aussi. Cependant, côté serveur, je ne vois qu'une partie de l'image que le client a voulu envoyer:

enter image description here

Il semble qu'il manque des octets. Quelle est la bonne façon d'envoyer une énorme quantité de données (images, autre type de fichier) en utilisant UNIQUEMENT des sockets?

16
yak

Je suppose que vous avez une raison particulière de le faire avec des sockets nus, comme l'auto-édification, ce qui signifie que je ne répondrai pas en disant "Vous avez accidentellement oublié d'utiliser simplement HTTP et Twisted", ce que vous avez peut-être entendu avant :-P. Mais vous devriez vraiment regarder les bibliothèques de niveau supérieur à un moment donné car elles sont beaucoup plus faciles!

Définir un protocole

Si tout ce que vous voulez, c'est envoyer une image, cela peut être simple:

  1. Client -> server: 8 bytes: gros endian, longueur de l'image.
  2. Client -> server: length bytes: toutes les données d'image.
  3. (Client <- server: 1 byte, value 0: indique que la transmission a été reçue - étape facultative, peu vous importe si vous utilisez TCP et supposez simplement que c'est fiable.)

Codez-le

server.py

import os
from socket import *
from struct import unpack


class ServerProtocol:

    def __init__(self):
        self.socket = None
        self.output_dir = '.'
        self.file_num = 1

    def listen(self, server_ip, server_port):
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.bind((server_ip, server_port))
        self.socket.listen(1)

    def handle_images(self):

        try:
            while True:
                (connection, addr) = self.socket.accept()
                try:
                    bs = connection.recv(8)
                    (length,) = unpack('>Q', bs)
                    data = b''
                    while len(data) < length:
                        # doing it in batches is generally better than trying
                        # to do it all in one go, so I believe.
                        to_read = length - len(data)
                        data += connection.recv(
                            4096 if to_read > 4096 else to_read)

                    # send our 0 ack
                    assert len(b'\00') == 1
                    connection.sendall(b'\00')
                finally:
                    connection.shutdown(SHUT_WR)
                    connection.close()

                with open(os.path.join(
                        self.output_dir, '%06d.jpg' % self.file_num), 'w'
                ) as fp:
                    fp.write(data)

                self.file_num += 1
        finally:
            self.close()

    def close(self):
        self.socket.close()
        self.socket = None

        # could handle a bad ack here, but we'll assume it's fine.

if __name__ == '__main__':
    sp = ServerProtocol()
    sp.listen('127.0.0.1', 55555)
    sp.handle_images()

client.py

from socket import *
from struct import pack


class ClientProtocol:

    def __init__(self):
        self.socket = None

    def connect(self, server_ip, server_port):
        self.socket = socket(AF_INET, SOCK_STREAM)
        self.socket.connect((server_ip, server_port))

    def close(self):
        self.socket.shutdown(SHUT_WR)
        self.socket.close()
        self.socket = None

    def send_image(self, image_data):

        # use struct to make sure we have a consistent endianness on the length
        length = pack('>Q', len(image_data))

        # sendall to make sure it blocks if there's back-pressure on the socket
        self.socket.sendall(length)
        self.socket.sendall(image_data)

        ack = self.socket.recv(1)

        # could handle a bad ack here, but we'll assume it's fine.

if __name__ == '__main__':
    cp = ClientProtocol()

    image_data = None
    with open('IMG_0077.jpg', 'r') as fp:
        image_data = fp.read()

    assert(len(image_data))
    cp.connect('127.0.0.1', 55555)
    cp.send_image(image_data)
    cp.close()
12
daphtdazz

Le problème est que vous n'incrémentez pas amount_received pour le premier bloc des données reçues.

Correction ci-dessous:

#!/usr/bin/env python

import random
import socket, select
from time import gmtime, strftime
from random import randint

imgcounter = 1
basename = "image%s.png"

Host = '127.0.0.1'
PORT = 2905

connected_clients_sockets = []

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind((Host, PORT))
server_socket.listen(10)

connected_clients_sockets.append(server_socket)

while True:

    read_sockets, write_sockets, error_sockets = select.select(connected_clients_sockets, [], [])

    for sock in read_sockets:

        if sock == server_socket:

            sockfd, client_address = server_socket.accept()
            connected_clients_sockets.append(sockfd)

        else:
            try:

                data = sock.recv(4096)
                txt = str(data)

                if data:

                    if data.startswith('SIZE'):
                        tmp = txt.split()
                        size = int(tmp[1])

                        print 'got size %s' % size

                        sock.sendall("GOT SIZE")

                    Elif data.startswith('BYE'):
                        sock.shutdown()

                    else :

                        myfile = open(basename % imgcounter, 'wb')
                        myfile.write(data)

                        amount_received = len(data) #  The fix!
                        while amount_received < size:
                            data = sock.recv(4096)
                            amount_received += len(data)

                            print amount_received

                            if not data:
                                break
                            myfile.write(data)
                        myfile.close()

                        sock.sendall("GOT IMAGE")
                        sock.shutdown()
            except:
                sock.close()
                connected_clients_sockets.remove(sock)
                continue
        imgcounter += 1
server_socket.close()
3
gipsy