Страницы

Поиск по вопросам

понедельник, 16 декабря 2019 г.

Отправка, принятие и сохранение больших файлов через socket в python

#python #python_3x #файлы #сокет


Учусь сейчас отправлять данные через сокеты. И процессе столкнулся со следующей ошибкой:
не получается отправить файл размером 5 мегабайт через сокет (вот ссылка на файл: https://upload.wikimedia.org/wikipedia/commons/2/20/Galaxies_of_the_Infrared_Sky_.jpg).
Файлы меньше 1 мегабайта отправляются без ошибок. Проблема появляется при чтении данных,
переданных клиентом серверу. Сервер, по непонятным причинам, не может выйти из цикла
в reliable_receive, и программа как будто зависает. Подскажите в чем проблема? Ниже код.

UPD: Я обновил код, воспользавшись ответом в комментариях (https://ru.stackoverflow.com/a/982881/324059),
но это как-то не работает. Пишет - TypeError: cannot convert 'NoneType' object to bytes
в строке part_len = int.from_bytes(self.readexactly(2), "big") в коде клиента. Помогите,
что делать?

Код сервера (отправляет клиенту название файла, который он должен отправить серверу,
затем сохраняет этот файл):

import socket
import json
import base64


class Listener:
    def __init__(self, ip, port):
        listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        listener.bind(('', port))  # IP
        listener.listen(0)
        print('[+] Waiting for incoming connection...')
        self.connection, address = listener.accept()
        print('[+] Got a connection from ' + str(address))

    def reliable_send(self, data):
        json_data = json.dumps(data)
        self.connection.send(json_data.encode())

    def readexactly(self, bytes_count):
        b = b''
        while len(b) < bytes_count:
            part = self.connection.recv(bytes_count - len(b))
            if not part:
                return b
            b += part

    def reliable_receive(self):
        b = b''
        while True:
            part_len = int.from_bytes(self.readexactly(2), "big")
            if not part_len:
                return json.loads(b)
            b += self.readexactly(part_len)

    def execute(self, command):
        self.reliable_send(command)
        return self.reliable_receive()

    def write_file(self, path, content):
        with open(path, 'wb') as file:
            file.write(base64.b64decode(content))
            return '[+] Download successful'

    def run(self):
        while True:
            command = input('>> ')
            result = self.execute(command)
            result = self.write_file(command, result)
            print(result)


my_listener = Listener('192.168.0.10', 4444)
my_listener.run()


Код клиента(получает название файла от сервера и отправляет серверу этот файл):

import socket
import json
import base64


class Client:
    def __init__(self, ip, port):
        self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.connection.connect((ip, port))

    def reliable_send(self, data):
        json_data = json.dumps(data)
        self.connection.send(json_data.encode())

    def readexactly(self, bytes_count):
        b = b''
        while len(b) < bytes_count:
            part = self.connection.recv(bytes_count - len(b))
            if not part:
                return b
            b += part

    def reliable_receive(self):
        b = b''
        while True:
            part_len = int.from_bytes(self.readexactly(2), "big")
            if not part_len:
                return json.loads(b)
            b += self.readexactly(part_len)

    def read_file(self, path):
        with open(path, 'rb') as file:
            return base64.encodebytes(file.read()).decode("utf-8")

    def run(self):
        while True:
            command = self.reliable_receive()
            command_result = self.read_file(command)

            self.reliable_send(command_result)


my_client = Client('192.168.0.9', 4444)
my_client.run()


А еще эти decode, encode, base64... я успел запутаться в них миллион раз, пока писал
код. Хотелось бы понять также, как, и можно ли вообще упростить это?  
    


Ответы

Ответ 1



@KonstantinSkokov дал Вам правильную наводку. Предлагаю Вам для решения этой проблемы использовать механизм Chunked transfer encoding. Пусть максимальная длина куска будет 0xffff (укладывается в 2 байта). Таким образом код для отправки данных будет выглядеть так: def reliable_send(self, data: bytes) -> None: """ Функция отправки данных в сокет Обратите внимание, что данные ожидаются сразу типа bytes """ # Разбиваем передаваемые данные на куски максимальной длины 0xffff (65535) for chunk in (data[_:_+0xffff] for _ in range(0, len(data), 0xffff)): self.connection.send(len(chunk).to_bytes(2, "big") # Отправляем длину куска (2 байта) self.connection.send(chunk) # Отправляем сам кусок self.connection.send(b"\x00\x00") # Обозначаем конец передачи куском нулевой длины Вспомогательная функция приёма определённого количества байт def readexactly(self, bytes_count: int) -> bytes: """ Функция приёма определённого количества байт """ b = b'' while len(b) < bytes_count: # Пока не получили нужное количество байт part = self.connection.recv(bytes_count - len(b)) # Получаем оставшиеся байты if not part: # Если из сокета ничего не пришло, значит его закрыли с другой стороны raise IOError("Соединение потеряно") b += part return b Функция приёма данных: def reliable_receive(self) -> bytes: """ Функция приёма данных Обратите внимание, что возвращает тип bytes """ b = b'' while True: part_len = int.from_bytes(self.readexactly(2), "big") # Определяем длину ожидаемого куска if part_len == 0: # Если пришёл кусок нулевой длины, то приём окончен return b b += self.readexactly(part_len) # Считываем сам кусок >>А еще эти decode, encode, base64... я успел запутаться в них миллион раз, пока писал код. Хотелось бы понять также, как, и можно ли вообще упростить это? - base64 нужен для кодирования любых байт в байты, которые можно смаппить на printable ascii символы, я не вижу у Вас задачи, для которой это бы потребовалось.

Ответ 2



Наверное, дело в строке data = self.connection.recv(1024) 1024 - как раз один мегабайт Вот тут похожая проблема, может поможет: https://stackoverflow.com/questions/42459499/what-is-the-proper-way-of-sending-a-large-amount-of-data-over-sockets-in-python

Комментариев нет:

Отправить комментарий