web-dev-qa-db-ja.com

Pythonのソケットを介して大量のデータを送信する適切な方法は何ですか?

最近、画像を送信するコード(クライアントとサーバー)を作成しました-クライアントは、単にsocketモジュールを使用してサーバーに画像をアップロードします: Send over image(のみ)Pythonでは、画像を開くことはできません

しかし、画像送信部分は今私が心配しているものです。これは私が使用している元の画像です:

enter image description here

サーバーコード(画像を受信する)には、次の行があります。

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()

しかし、これが最善の方法だとは思いません。代わりに、データをまとめて受信するようにサーバーを実装する必要があると思います。

#!/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()

しかし、これを行うと、サーバーは次を出力します。

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

そして、ハングしているように見え、クライアントもハングします。ただし、サーバー側では、クライアントが送信したい画像の一部しか見ることができません。

enter image description here

いくつかのバイトが欠落しているようです。 ONLYソケットを使用して大量のデータ(画像、他の種類のファイル)を送信する適切な方法は何ですか?

16
yak

自己啓発など、裸のソケットを使用してこれを行う特別な理由があると仮定しています。つまり、「HTTPとTwistedを使用するのを忘れてしまった」と答えることはできません。 前に聞いた :-Pしかし、実際には、より簡単なため、ある時点で高レベルのライブラリを調べる必要があります。

プロトコルを定義する

必要なのが画像を送信することだけであれば、それは簡単です:

  1. Client -> server: 8 bytes:ビッグエンディアン、画像の長さ。
  2. Client -> server: length bytes:すべての画像データ。
  3. Client <- server: 1 byte, value 0:受信した送信を示します-TCPを使用し、信頼できると仮定するだけでもかまいません。)

コーディングする

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

問題は、amount_received受信したデータの最初のチャンク。

以下を修正します。

#!/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