diff options
| author | Андрей Гущин <saintruler@gmail.com> | 2019-06-06 10:01:07 +0300 |
|---|---|---|
| committer | Андрей Гущин <saintruler@gmail.com> | 2019-06-06 10:01:07 +0300 |
| commit | dfbfd62186cb94abd79a2da3756fb6fe7d66f3dd (patch) | |
| tree | 72c1874581c490e65f87113cf7c0ffb43cb34f0b | |
| parent | f3512425265d2432c1791850fee3fa313267432b (diff) | |
| parent | 7f2e25edde7c4e8bc60514156abf28014a3f0c3c (diff) | |
Merge branch 'day4_task4' into 'master'
День 4, задача 4
See merge request saintruler/trainee!24
| -rw-r--r-- | day4/task4/client.py | 135 | ||||
| -rw-r--r-- | day4/task4/server.py | 125 |
2 files changed, 260 insertions, 0 deletions
diff --git a/day4/task4/client.py b/day4/task4/client.py new file mode 100644 index 0000000..b6f0ab8 --- /dev/null +++ b/day4/task4/client.py @@ -0,0 +1,135 @@ +import socket +from time import sleep +import struct +import json +import shelve + +try: + from tqdm import tqdm + TQDM = True +except ImportError: + print('Для вывода прогресса загрузки файлов установите библиотеку tqdm') + TQDM = False + + +DATATYPE_SIZE = 4 # int +CHUNK_SIZE = 4096000 +HOST, PORT = ADDR = 'localhost', 6000 + + +class Client: + def __init__(self, server_host, server_port): + self.server = socket.socket() + self.db = shelve.open('backdoor.db') + + address = server_host, server_port + cnt = 0 + while cnt < 10: + try: + cnt += 1 + self.server.connect(address) + + except ConnectionRefusedError: + print('Не удалось подключиться к серверу. Следующая попытка через 3 секунды...') + sleep(3) + + else: + break + + else: + print('Не удалось подключиться к серверу через 10 попыток.\nОстанавливаемся...') + self.server.close() + + def start(self): + while True: + try: + data = input('Введите команду:\n') + self.handle_input(data) + + except ValueError as err: + print(err) + + except KeyboardInterrupt: + break + + self.server.close() + + def handle_input(self, data): + cmd, args = data.split(' ', maxsplit=1) + + if cmd == 'get': + self.download_file(args) + + elif cmd == 'cp': + paths = args.split() + if len(paths) != 2: + raise ValueError('Неверное количество путей в параметрах.') + + src, dest = paths + + # Если файл не присутствует в БД, то предварительно его загружаем + if src not in self.db: + self.download_file(src) + + # При любых ошибках при загрузке файла download_file выкинет исключение и исполнение + # не дойдет до этого блока, поэтому можно не проверять наличие src в БД + with open(dest, 'wb') as f: + f.write(self.db[src]) + + else: + raise ValueError('Команда не распознана') + + def download_file(self, path): + response = self.make_get_request(path) + if response['status'] == 0: + print(f'Загружаем файл размером {response["size"]} байт...') + + file_data = self.receive_data(int(response['size']), use_tqdm=True) + self.db[path] = file_data + + print('Загрузка завершена') + + elif response['status'] == 1: + raise ValueError(response['reason']) + + elif response['status'] == 2: + raise ValueError(f'Неизвестная ошибка: {response["reason"]}') + + def make_get_request(self, path): + packet = Client.pack_str(json.dumps({'method': 'get', 'path': path})) + self.server.sendall(packet) + return Client.parse_response(self.receive_data(DATATYPE_SIZE)) + + def receive_data(self, data_size, use_tqdm=False): + data = b'' + + # С копипастой надо бы что-то сделать, но я не знаю что + if not use_tqdm or not TQDM: + while len(data) < data_size: + (packet_size,) = struct.unpack('I', self.server.recv(DATATYPE_SIZE)) + data += self.server.recv(packet_size) + if not data: + raise ConnectionError('Connection was closed') + + elif TQDM: + for count in tqdm(range(0, data_size, CHUNK_SIZE)): + (packet_size,) = struct.unpack('I', self.server.recv(DATATYPE_SIZE)) + data += self.server.recv(packet_size) + if not data: + raise ConnectionError('Connection was closed') + + return data + + @staticmethod + def pack_str(data): + encoded = data.encode() + return struct.pack('I', len(encoded)) + encoded + + @staticmethod + def parse_response(data): + return json.loads(data.decode()) + + +if __name__ == '__main__': + client = Client(HOST, PORT) + client.start() diff --git a/day4/task4/server.py b/day4/task4/server.py new file mode 100644 index 0000000..d013b35 --- /dev/null +++ b/day4/task4/server.py @@ -0,0 +1,125 @@ +import socket +import struct +import json + + +HOST, PORT = ADDR = '0.0.0.0', 6000 +CHUNK_SIZE = 4096000 +DATATYPE_SIZE = 4 # int + + +def pack_str(data: str): + encoded = data.encode() + return struct.pack('I', len(encoded)) + encoded + + +def pack_bytes(data: bytes): + return struct.pack('I', len(data)) + data + + +def handle_request(data, connection): + request = json.loads(data) + + # Хотя метод только один, но все равно лучше + # сразу написать расширяемое приложение + if request['method'] == 'get': + path = request['path'] + + try: + with open(path, 'rb') as f: + file_data = f.read() + + except FileNotFoundError: + header = json.dumps({'status': 1, 'reason': 'Файл не найден'}) + connection.sendall(pack_str(header)) + + except PermissionError: + header = json.dumps({'status': 1, 'reason': 'Файл не доступен для чтения'}) + connection.sendall(pack_str(header)) + + # Я знаю, что это плохо, но сервер не должен падать, + # а других ошибок я не смог вспомнить + except Exception as e: + header = json.dumps({'status': 2, 'reason': f'{type(e)}: {e}'}) + connection.sendall(pack_str(header)) + + else: + length = len(file_data) + header = json.dumps({'status': 0, 'size': length, 'path': request['path']}) + connection.sendall(pack_str(header)) + print(f'Started sending file of size {length} bytes...') + i = 0 + while i < length: + packet = pack_bytes(file_data[i: i + CHUNK_SIZE]) + connection.sendall(packet) + i += CHUNK_SIZE + print(i / length * 100, '%') + print(f'Transmission ended') + + +def get_data(connection): + buffer = b'' + # Считываем размер пакета + while len(buffer) < DATATYPE_SIZE: + data = connection.recv(DATATYPE_SIZE - len(buffer)) + if not data: + raise ConnectionError('Connection was closed') + + buffer += data + + size_packed, buffer = buffer[:DATATYPE_SIZE], buffer[DATATYPE_SIZE:] + if not size_packed: + raise ConnectionError('Connection was closed') + + (size,) = struct.unpack('I', size_packed) + data = connection.recv(size - len(buffer)) + return buffer + data + + +def handle_connection(connection, address): + while True: + try: + data = get_data(connection) + + except ConnectionResetError: + print(f'Connection with {address[0]} was unexpectedly closed') + break + + except ConnectionError: + print(f'Connection with {address[0]} was ended') + break + + except KeyboardInterrupt: + raise KeyboardInterrupt() + + else: + if not data: + print(f'Connection with {address[0]} was ended') + break + handle_request(data.decode(), connection) + + +def main(): + sock = socket.socket() + sock.bind(ADDR) + sock.listen(5) + + while True: + print('Listening for new connections...') + try: + connection, address = sock.accept() + print(f'Got a connection from {address[0]}') + handle_connection(connection, address) + + except ConnectionResetError: + print(f'Connection with {address[0]} was unexpectedly closed') + + except KeyboardInterrupt: + print('Stopping server...') + sock.close() + print('Server stopped') + break + + +if __name__ == '__main__': + main() |