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