#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
Комментариев нет:
Отправить комментарий