Проблема отправки двоичных файлов через сокеты, Python - PullRequest
0 голосов
/ 09 октября 2018

Я пытаюсь написать программу, которая передает двоичные файлы с клиента на сервер.Вот код:

Клиент (отправить файл)

  def send_file(self,filename):
        print("Sending: " + filename)
        size = self.BUFFER_SIZE
        with open(filename,'rb') as f:
            raw = f.read().decode()
        buffer = [raw[i:i + size] for i in range(0, len(raw), size)]
        for x in range(len(buffer)):
            self.sock.sendall(buffer[x].encode())

        return

Сервер (файл recv)

def recv_file(self, conn, filename):
    packet = ""
    buffer = ""
    while True:
        buffer = conn.recv(self.BUFFER_SIZE)
        packet = packet + str(buffer.decode())
        if not len(buffer) == self.BUFFER_SIZE:
            break
    with open(filename, 'wb') as f:
        f.write(bytes(packet.encode()))
    #print(packet)
    return 

Таким образом я могу передавать текстовые файлы, но когда у меня естьчтобы передать JPEG или любой другой тип файла, он зависает в цикле.Может кто-нибудь объяснить, пожалуйста, почему?Я новичок в Пи, и я пытаюсь научиться

Ответы [ 2 ]

0 голосов
/ 09 октября 2018

В качестве дополнения к сообщению ShadowRanger, если вы хотите сохранить разбиение файла без использования socket.sendfile, вы можете использовать несколько приемов для очистки кода и уменьшения объема используемой памяти.

Процесс отправки довольно прост, поскольку мы скопировали процесс отправки размера файла из ShadowRanger и добавили очень простой цикл для отправки фрагментов данных, пока блок не окажется пустым (конец файла).

def send_file(self,filename):
    print("Sending: " + filename)
    #send file size as big endian 64 bit value (8 bytes)
    self.sock.sendall(os.stat(filename).st_size.tobytes(8,'big'))
    with open(filename,'rb') as f: #open our file to read
        while True:
            chunk = f.read(self.BUFFER_SIZE) #get next chunk
            if not chunk: #empty chunk indicates EOF
                break
            self.sock.sendall(chunk) #send the chunk

Получение файла также очень просто, с тем же самым процессом, чтобы прочитать ожидаемый размер файла в начале, затем цикл для чтения данных в этот файл, пока мы не достигнем нашего ожидаемого размера.Затем мы используем f.tell(), так как получаем данные, чтобы легко узнать, был ли весь файл отправлен.

def recv_file(self, conn, filename):
    # file size transfer copied from ShadowRanger
    # Get the expected length (eight bytes long, always)
    expected_size = b"" #buffer to read in file size
    while len(expected_size) < 8: #while buffer is smaller than 8 bytes
        more_size = conn.recv(8 - len(expected_size)) #read up to remaining bytes
        if not more_size: #nothing was read
            raise Exception("Short file length received")
        expected_size += more_size #extend buffer
    expected_size = int.from_bytes(expected_size, 'big') #Convert to int, the expected file length
    with open(filename, 'wb') as f: #open our file to write
        while f.tell() < expected_size: #while it's smaller than our expected size
            bytes_recvd = conn.recv() #read any available data 
            f.write(bytes_recvd)
0 голосов
/ 09 октября 2018

Он не должен зависать, если обе стороны имеют одинаковую кодировку локали, но он может легко умереть за исключением.

Вы читаете и отправляете как двоичный файл (хорошо), но необъяснимым образом decode -вернувшись к str, затем encode вернувшись к bytes (плохо).Проблема в том, что произвольные двоичные данные не гарантируются для декодирования в любой заданной локали;если ваша кодировка локали UTF-8, скорее всего, это не законно.Если это latin-1, это законно, но бессмысленно.

Хуже того, если ваш клиент и сервер имеют разные кодировки локали, результат декодирования может быть различным на каждой стороне (и, следовательно, длины не будут совпадать).

Используйте bytes последовательно, не конвертируйте в и из строк, и настройки локали не будут иметь значения.Ваш код также будет работать быстрее.Вы также должны на самом деле отправить длину файла заранее;Ваш цикл надеется, что recv вернет короткую длину только после завершения файла, но если:

  1. Файл является точным кратным размеру буфера, или
  2. случается, что сокет отправляет данные порциями, которые не соответствуют размеру буфера

, каждый из которых может получить короткие recv результаты, по совпадению в случае № 2 и детерминистически в случае № 1.

Более безопасный подход заключается в том, чтобы на самом деле префикс вашей передачи иметь длину файла, а не надеяться, что порция работает как ожидалось:

def send_file(self,filename):
    print("Sending:", filename)
    with open(filename, 'rb') as f:
        raw = f.read()
    # Send actual length ahead of data, with fixed byteorder and size
    self.sock.sendall(len(raw).to_bytes(8, 'big'))
    # You have the whole thing in memory anyway; don't bother chunking
    self.sock.sendall(raw)

def recv_file(self, conn, filename):
    # Get the expected length (eight bytes long, always)
    expected_size = b""
    while len(expected_size) < 8:
        more_size = conn.recv(8 - len(expected_size))
        if not more_size:
            raise Exception("Short file length received")
        expected_size += more_size

    # Convert to int, the expected file length
    expected_size = int.from_bytes(expected_size, 'big')

    # Until we've received the expected amount of data, keep receiving
    packet = b""  # Use bytes, not str, to accumulate
    while len(packet) < expected_size:
        buffer = conn.recv(expected_size - len(packet))
        if not buffer:
            raise Exception("Incomplete file received")
        packet += buffer
    with open(filename, 'wb') as f:
        f.write(packet)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...