diff --git a/Lisson_8/Tests/__init__.py b/Lisson_8/Tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Lisson_8/Tests/test_client.py b/Lisson_8/Tests/test_client.py new file mode 100644 index 0000000..5623f8b --- /dev/null +++ b/Lisson_8/Tests/test_client.py @@ -0,0 +1,68 @@ +# from pprint import pprint +import sys +# pprint(sys.path) +sys.path.append('../') # Выбрал вариант покороче +# pprint(sys.path) +# import os +# sys.path.append(os.path.join(os.getcwd(), '..')) +# pprint(sys.path) +import unittest +from client import create_presence, process_server_answer +from common.variables import USER, ACCOUNT_NAME, ACTION, TIME, PRESENCE, ERROR, RESPONSE + + +class TestCreatePresence(unittest.TestCase): + + def setUp(self): + # print('Начало теста') + pass + + def tearDown(self): + # print('Конец теста') + pass + + def test_create_presence_default_account_name(self): + self.assertEqual(create_presence()[USER][ACCOUNT_NAME], 'Guest', + 'Не праильно назначено default_name') + + def test_create_presence_account_name(self): + self.assertEqual(create_presence('Alex')[USER][ACCOUNT_NAME], 'Alex', + 'Не правильно назначено account_name !!!') + + def test_create_presence_action(self): + self.assertEqual(create_presence()[ACTION], 'presence', + 'Не правильное значение action в словаре !!!') + + def test_create_presence_time(self): + test = create_presence() + test[TIME] = 1.1 + self.assertEqual(test, {ACTION: PRESENCE, TIME: 1.1, USER: {ACCOUNT_NAME: 'Guest'}}, + 'Не правильное значение time !!!') + + +class TestProcessServerAnswer(unittest.TestCase): + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_process_server_answer_200(self): + test_message = {RESPONSE: 200} + self.assertEqual(process_server_answer(test_message), '200 : OK', 'Не верный ответ на код 200') + + def test_process_server_answer_400(self): + test_message = {RESPONSE: 400, + ERROR: 'Bad Request' + } + self.assertEqual(process_server_answer(test_message), f'400 : {test_message[ERROR]}', + 'Не верный ответ на код отличный от 200') + + def test_process_server_answer_no_response(self): + self.assertRaises(ValueError, process_server_answer, {ERROR: 'Bad Request'}) + + +if __name__ == "__main__": + unittest.main() + diff --git a/Lisson_8/Tests/test_server.py b/Lisson_8/Tests/test_server.py new file mode 100644 index 0000000..f874e83 --- /dev/null +++ b/Lisson_8/Tests/test_server.py @@ -0,0 +1,48 @@ +import sys +import os +sys.path.append(os.path.join(os.getcwd(), '..')) +import unittest +from server import process_client_message +from common.variables import RESPONSE, ERROR, ACTION, PRESENCE, TIME, USER, ACCOUNT_NAME + + +class TestProcessClientMessage(unittest.TestCase): + correct_dict = {RESPONSE: 200} + error_dict = { + RESPONSE: 400, + ERROR: 'Bad Request' + } + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_process_client_message_ok_check(self): + self.assertEqual(process_client_message({ACTION: PRESENCE, TIME: 1.1, USER: {ACCOUNT_NAME: 'Guest'}}), + self.correct_dict, 'Ошибка при корректном запросе !!!') + + def test_process_client_message_no_action(self): + self.assertEqual(process_client_message({TIME: 1.1, USER: {ACCOUNT_NAME: 'Guest'}}), + self.error_dict, 'Ошибка нет action !!!') + + def test_process_client_message_no_correct_action(self): + self.assertEqual(process_client_message({ACTION: 'Test', TIME: 1.1, USER: {ACCOUNT_NAME: 'Guest'}}), + self.error_dict, 'Ошибка при неизвестном action !!!') + + def test_process_client_message_no_time(self): + self.assertEqual(process_client_message({ACTION: PRESENCE, USER: {ACCOUNT_NAME: 'Guest'}}), + self.error_dict, 'Ошибка при отсутствии TIME !!!') + + def test_process_client_message_no_user(self): + self.assertEqual(process_client_message({ACTION: PRESENCE, TIME: 1.1}), + self.error_dict, 'Ошибка при отсутствии USER !!!') + + def test_process_client_message_unknown_user(self): + self.assertEqual(process_client_message({ACTION: PRESENCE, TIME: 1.1, USER: {ACCOUNT_NAME: 'test'}}), + self.error_dict, 'Ошибка при неизвестном USER !!!') + + +if __name__ == "__main__": + unittest.main() diff --git a/Lisson_8/Tests/test_unit.py b/Lisson_8/Tests/test_unit.py new file mode 100644 index 0000000..82f3951 --- /dev/null +++ b/Lisson_8/Tests/test_unit.py @@ -0,0 +1,71 @@ +import sys +import os +sys.path.append(os.path.join(os.getcwd(), '..')) +import unittest +from common.variables import ENCODING, MAX_PACKAGE_LENGTH, ACTION, PRESENCE, \ + TIME, USER, ACCOUNT_NAME, RESPONSE, ERROR +import json +from common.utils import send_message, get_message + + +class TestSocket: + def __init__(self, test_dict): + self.test_dict = test_dict + self.encoded_message = None + self.received_message = None + + def send(self, message_to_send): + json_test_message = json.dumps(self.test_dict) + self.encoded_message = json_test_message.encode(ENCODING) + self.received_message = message_to_send + + def recv(self, max_len): + json_test_message = json.dumps(self.test_dict) + return json_test_message.encode(ENCODING) + + +class TestUtils(unittest.TestCase): + + test_dict_send = { + ACTION: PRESENCE, + TIME: 1.1, + USER: { + ACCOUNT_NAME: 'test_name' + } + } + + test_dict_recv_correct = {RESPONSE: 200} + test_dict_recv_error = { + RESPONSE: 400, + ERROR: 'Bad Request' + } + + def setUp(self): + pass + + def tearDown(self): + pass + + def test_get_message_correct(self): + test_socket_correct = TestSocket(self.test_dict_recv_correct) + test_socket_error = TestSocket(self.test_dict_recv_error) + self.assertEqual(get_message(test_socket_correct), self.test_dict_recv_correct) + + def test_get_message_error(self): + test_socket_correct = TestSocket(self.test_dict_recv_correct) + test_socket_error = TestSocket(self.test_dict_recv_error) + self.assertEqual(get_message(test_socket_error), self.test_dict_recv_error) + + def test_send_message(self): + test_socket = TestSocket(self.test_dict_send) + send_message(test_socket, self.test_dict_send) + self.assertEqual(test_socket.encoded_message, test_socket.received_message) + + def test_send_message_raise(self): + test_socket = TestSocket(self.test_dict_send) + send_message(test_socket, self.test_dict_send) + self.assertRaises(TypeError, send_message, test_socket, "Неправильный словарь") + + +if __name__ == '__main__': + unittest.main() diff --git a/Lisson_8/client.py b/Lisson_8/client.py new file mode 100644 index 0000000..c42c747 --- /dev/null +++ b/Lisson_8/client.py @@ -0,0 +1,248 @@ +"""Программа - клиент""" + +import argparse +import logging +import threading + +import log.client_log_config +from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR +import unittest +import sys +import time +import json +from common.variables import DEFAULT_IP_ADDRESS, DEFAULT_PORT, ACTION, PRESENCE, TIME, \ + USER, ACCOUNT_NAME, RESPONSE, ERROR, MESSAGE, MESSAGE_TEXT, SENDER, DESTINATION, EXIT +from common.utils import get_message, send_message +from errors import ReqFieldMissingError, NonDictInputError, ServerError +from decorators.decors_def import log +import inspect +from functools import wraps + +# Инициализируем клиентский логер +CLIENT_LOGGER = logging.getLogger('client') + + +# Создает запрос о присутствии клиента +@log +def create_presence(account_name): + """Функция генерирует запрос о присутствии клиента""" + message = { + ACTION: PRESENCE, + TIME: time.time(), + USER: { + ACCOUNT_NAME: account_name, + } + } + CLIENT_LOGGER.debug(f'Сформировано {PRESENCE} сообщение для пользователя {account_name}') + return message + + +@log +def create_message(sock, account_name='Guest'): + """Функция запрашивает текст сообщения и возвращает его. + Так же завершает работу при вводе подобной комманды""" + to_user = input('Введите получателя сообщения: ') + message = input('Введите сообщение для отправки: ') + message_dict = { + ACTION: MESSAGE, + SENDER: account_name, + DESTINATION: to_user, + TIME: time.time(), + MESSAGE_TEXT: message + } + CLIENT_LOGGER.debug(f'Сформирован словарь сообщения: {message_dict}') + try: + send_message(sock, message_dict) + CLIENT_LOGGER.info(f'Отправлено сообщение для пользователя {to_user}') + except Exception as e: + print(e) + CLIENT_LOGGER.critical('Потеряно соединение с сервером.') + sys.exit(1) + + +@log +def create_exit_message(account_name): + """Функция создаёт словарь с сообщением о выходе""" + return { + ACTION: EXIT, + TIME: time.time(), + ACCOUNT_NAME: account_name + } + + +@log +def message_from_server(sock, my_username): + """Функция - обработчик сообщений других пользователей, поступающих с сервера""" + while True: + try: + message = get_message(sock) + if ACTION in message and message[ACTION] == MESSAGE and \ + SENDER in message and DESTINATION in message \ + and MESSAGE_TEXT in message and message[DESTINATION] == my_username: + print(f'\nПолучено сообщение от пользователя ' + f'{message[SENDER]}: {message[MESSAGE_TEXT]}') + CLIENT_LOGGER.info(f'Получено сообщение от пользователя ' + f'{message[SENDER]}: {message[MESSAGE_TEXT]}') + else: + CLIENT_LOGGER.error(f'Получено некорректное сообщение с сервера: {message}') + except (OSError, ConnectionError, ConnectionAbortedError, + ConnectionResetError, json.JSONDecodeError): + CLIENT_LOGGER.critical(f'Потеряно соединение с сервером.') + break + +# Разбирает ответ сервера +@log +def process_server_answer(message): + CLIENT_LOGGER.debug(f'Разбор сообщения от сервера: {message}') + if RESPONSE in message: + if message[RESPONSE] == 200: + return '200 : OK' + elif message[RESPONSE] == 400: + raise ServerError(f'400 : {message[ERROR]}') + raise ReqFieldMissingError(RESPONSE) + + +def print_help(): + """Функция выводящяя справку по использованию""" + print('Поддерживаемые команды:') + print('message - отправить сообщение. Кому и текст будет запрошены отдельно.') + print('help - вывести подсказки по командам') + print('exit - выход из программы') + + +@log +def user_interactive(sock, username): + """Функция взаимодействия с пользователем, запрашивает команды, отправляет сообщения""" + print_help() + while True: + command = input('Введите команду: ') + if command == 'message': + create_message(sock, username) + elif command == 'help': + print_help() + elif command == 'exit': + send_message(sock, create_exit_message(username)) + print('Завершение соединения.') + CLIENT_LOGGER.info('Завершение работы по команде пользователя.') + # Задержка неоходима, чтобы успело уйти сообщение о выходе + time.sleep(0.5) + break + else: + print('Команда не распознана, попробойте снова. help - вывести поддерживаемые команды.') + + +@log +def arg_parser(): + """Парсер аргументов коммандной строки""" + parser = argparse.ArgumentParser() + parser.add_argument( + 'address', + default=DEFAULT_IP_ADDRESS, + nargs='?', + help='ip address' + ) + parser.add_argument( + 'port', + default=DEFAULT_PORT, + type=int, + nargs='?', + help='port' + ) + parser.add_argument( + '-n', + '--name', + default=None, + nargs='?', + help='name' + ) + namespace = parser.parse_args(sys.argv[1:]) + server_address = namespace.address + server_port = namespace.port + client_name = namespace.name + + # Проверяем номер порта + if not 1023 < server_port < 65536: + CLIENT_LOGGER.critical( + f'Попытка запуска client с некоректным номером порта: {server_port}.' + f'Адресс порта должен быть от 1024 до 65535. Программа завершена.' + ) + sys.exit(1) + + return server_address, server_port, client_name + + +def main(): + ''' + Загружаем параметры командной строки + server.py 127.0.0.1 8888 -n name + ''' + server_address, server_port, client_name = arg_parser() + + """Сообщаем о запуске""" + print(f'Консольный месседжер. Клиентский модуль. Имя пользователя: {client_name}') + + # Если имя пользователя не было задано, необходимо запросить пользователя. + if not client_name: + client_name = input('Введите имя пользователя: ') + + CLIENT_LOGGER.info( + f'Запущен клиент с парамертами: адрес сервера: {server_address}, ' + f'порт: {server_port}, режим работы: {client_name}' + ) + + # Инициализируем сокет + try: + serv_sock = socket(AF_INET, SOCK_STREAM) + serv_sock.connect((server_address, server_port)) + send_message(serv_sock, create_presence(client_name)) + answer = process_server_answer(get_message(serv_sock)) + CLIENT_LOGGER.info(f'Установлено соединение с сервером. Ответ сервера: {answer}') + print(f'Установлено соединение с сервером.') + except json.JSONDecodeError: + CLIENT_LOGGER.error('Не удалось декодировать полученную Json строку.') + print('Не удалось декодировать сообщение сервера.') + sys.exit(1) + except ServerError as error: + CLIENT_LOGGER.error(f'При установке соединения сервер вернул ошибку: {error.text}') + sys.exit(1) + except ReqFieldMissingError as missing_error: + CLIENT_LOGGER.error( + f'В ответе сервера отсутствует необходимое поле ' + f'{missing_error.missing_field}' + ) + sys.exit(1) + except (ConnectionRefusedError, ConnectionError): + CLIENT_LOGGER.critical( + f'Не удалось подключиться к серверу {server_address}:{server_port}, ' + f'конечный компьютер отверг запрос на подключение.' + ) + sys.exit(1) + except NonDictInputError: + CLIENT_LOGGER.error(f'Ответ сервера не является словарем') + sys.exit(1) + else: + # Если соединение с сервером установлено корректно, + # запускаем клиентский процесс приёма сообщений + receiver = threading.Thread(target=message_from_server, args=(serv_sock, client_name)) + receiver.daemon = True + receiver.start() + + # затем запускаем отправку сообщений и взаимодействие с пользователем. + user_interface = threading.Thread(target=user_interactive, args=(serv_sock, client_name)) + user_interface.daemon = True + user_interface.start() + CLIENT_LOGGER.debug('Запущены процессы') + + # Watchdog основной цикл, если один из потоков завершён, + # то значит или потеряно соединение или пользователь + # ввёл exit. Поскольку все события обработываются в потоках, + # достаточно просто завершить цикл. + while True: + time.sleep(1) + if receiver.is_alive() and user_interface.is_alive(): + continue + break + + +if __name__ == '__main__': + main() diff --git a/Lisson_8/common/__init__.py b/Lisson_8/common/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Lisson_8/common/utils.py b/Lisson_8/common/utils.py new file mode 100644 index 0000000..763ff4e --- /dev/null +++ b/Lisson_8/common/utils.py @@ -0,0 +1,28 @@ +"""Утилиты""" + +from common.variables import MAX_PACKAGE_LENGTH, ENCODING +import json +from decorators.decors_def import log + + +# Принимает и декодирует сообщение, возвращает словарь +@log +def get_message(client): + encoded_response = client.recv(MAX_PACKAGE_LENGTH) + if isinstance(encoded_response, bytes): + json_response = encoded_response.decode(ENCODING) + response = json.loads(json_response) + if isinstance(response, dict): + return response + raise ValueError + raise ValueError + + +# Кодирует и отправляет сообщение +@log +def send_message(sock, message): + if not isinstance(message, dict): + raise TypeError + json_message = json.dumps(message) + encoded_message = json_message.encode(ENCODING) + sock.send(encoded_message) diff --git a/Lisson_8/common/variables.py b/Lisson_8/common/variables.py new file mode 100644 index 0000000..617932c --- /dev/null +++ b/Lisson_8/common/variables.py @@ -0,0 +1,47 @@ +"""Константы""" + +import logging + +# Порт поумолчанию для сетевого ваимодействия +DEFAULT_PORT = 7777 +# IP адрес по умолчанию для подключения клиента +DEFAULT_IP_ADDRESS = '127.0.0.1' +# Максимальная очередь подключений +MAX_CONNECTIONS = 5 +# Максимальная длинна сообщения в байтах +MAX_PACKAGE_LENGTH = 1024 +# Кодировка проекта +ENCODING = 'utf-8' + +# Прококол JIM основные ключи: +ACTION = 'action' +TIME = 'time' +USER = 'user' +ACCOUNT_NAME = 'account_name' +SENDER = 'sender' +DESTINATION = 'to' + +# Прочие ключи, используемые в протоколе +PRESENCE = 'presence' +RESPONSE = 'response' +ERROR = 'error' +MESSAGE = 'message' +MESSAGE_TEXT = 'mess_text' +EXIT = 'exit' + +RESPONDEFAULT_IP_ADDRESSSE = 'respondefault_ip_addressse' + +# Текущий уровень логирования +LEVEL_LOGGING = logging.DEBUG + +# Включение и выключение декоратора @log +ENABLE_TRACING = True + +# Словари - ответы: +# 200 +RESPONSE_200 = {RESPONSE: 200} +# 400 +RESPONSE_400 = { + RESPONSE: 400, + ERROR: None +} diff --git a/Lisson_8/decorators/__init__.py b/Lisson_8/decorators/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Lisson_8/decorators/decors_def.py b/Lisson_8/decorators/decors_def.py new file mode 100644 index 0000000..c757a93 --- /dev/null +++ b/Lisson_8/decorators/decors_def.py @@ -0,0 +1,23 @@ +import inspect +import sys +sys.path.append('../') +from common.variables import ENABLE_TRACING +from functools import wraps +import logging, time + + +def log(func): + """Функция декоратор""" + if ENABLE_TRACING: + @wraps(func) + def callfunc(*args, **kwargs): + logger_name = 'server' if 'server.py' in sys.argv[0] else 'client' + LOGGER = logging.getLogger(logger_name) + result = func(*args, **kwargs) + LOGGER.debug(f'Была вызвана функция: <{func.__name__}()>. ' + f'Вызов из модуля: <{func.__module__}>. ' + f'Вызов из функции: <{inspect.stack()[1][3]}()>.') + return result + return callfunc + else: + return func diff --git a/Lisson_8/errors.py b/Lisson_8/errors.py new file mode 100644 index 0000000..808f03a --- /dev/null +++ b/Lisson_8/errors.py @@ -0,0 +1,37 @@ +"""Ошибки""" + + +class IncorrectDataReceivedError(Exception): + """ + Исключение - некорректные данные получены от сокета + """ + def __str__(self): + return 'Принято некорректное сообщение от удалённого компьютера.' + + +class NonDictInputError(Exception): + """ + Исключение - аргумент функции не словарь + """ + def __str__(self): + return 'Аргумент функции должен быть словарём.' + + +class ReqFieldMissingError(Exception): + """ + Ошибка - отсутствует обязательное поле в принятом словаре + """ + def __init__(self, missing_field): + self.missing_field = missing_field + + def __str__(self): + return f'В принятом словаре отсутствует обязательное поле {self.missing_field}.' + + +class ServerError(Exception): + """Исключение - ошибка сервера""" + def __init__(self, text): + self.text = text + + def __str__(self): + return self.text diff --git a/Lisson_8/launcher.py b/Lisson_8/launcher.py new file mode 100644 index 0000000..9c25bd4 --- /dev/null +++ b/Lisson_8/launcher.py @@ -0,0 +1,29 @@ +"""Программа-Лаунчер""" + +import subprocess + +PROCESS = [] + +while True: + ACTION = input('Выберите действие: q - выход, ' + 's - запустить сервер и клиенты, ' + 'x - закрыть все окна: ') + + if ACTION == 'q': + break + elif ACTION == 's': + PROCESS.append(subprocess.Popen('python server.py', + creationflags=subprocess.CREATE_NEW_CONSOLE)) + for i in range(1, 4): + PROCESS.append(subprocess.Popen(f'python client.py -n user{i}', + creationflags=subprocess.CREATE_NEW_CONSOLE)) + # PROCESS.append(subprocess.Popen('python client.py -n user2', + # creationflags=subprocess.CREATE_NEW_CONSOLE)) + # PROCESS.append(subprocess.Popen('python client.py -n user3', + # creationflags=subprocess.CREATE_NEW_CONSOLE)) + # PROCESS.append(subprocess.Popen('python client.py -n user3', + # creationflags=subprocess.CREATE_NEW_CONSOLE)) + elif ACTION == 'x': + while PROCESS: + VICTIM = PROCESS.pop() + VICTIM.kill() diff --git a/Lisson_8/log/__init__.py b/Lisson_8/log/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Lisson_8/log/client.log b/Lisson_8/log/client.log new file mode 100644 index 0000000..92f218b --- /dev/null +++ b/Lisson_8/log/client.log @@ -0,0 +1,77 @@ +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,460 INFO client.py Запущен клиент с парамертами: адрес сервера: 127.0.0.1, порт: 7777, режим работы: user3 +2022-03-30 20:07:44,460 DEBUG client.py Сформировано presence сообщение для пользователя user3 +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,460 DEBUG client.py Разбор сообщения от сервера: {'response': 200} +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,460 INFO client.py Установлено соединение с сервером. Ответ сервера: 200 : OK +2022-03-30 20:07:44,475 DEBUG client.py Запущены процессы +2022-03-30 20:07:44,475 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,475 INFO client.py Запущен клиент с парамертами: адрес сервера: 127.0.0.1, порт: 7777, режим работы: user1 +2022-03-30 20:07:44,491 DEBUG client.py Сформировано presence сообщение для пользователя user1 +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,491 DEBUG client.py Разбор сообщения от сервера: {'response': 200} +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,491 INFO client.py Установлено соединение с сервером. Ответ сервера: 200 : OK +2022-03-30 20:07:44,491 DEBUG client.py Запущены процессы +2022-03-30 20:07:44,507 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,507 INFO client.py Запущен клиент с парамертами: адрес сервера: 127.0.0.1, порт: 7777, режим работы: user2 +2022-03-30 20:07:44,507 DEBUG client.py Сформировано presence сообщение для пользователя user2 +2022-03-30 20:07:44,507 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,507 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:45,021 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:45,021 DEBUG client.py Разбор сообщения от сервера: {'response': 200} +2022-03-30 20:07:45,021 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:45,021 INFO client.py Установлено соединение с сервером. Ответ сервера: 200 : OK +2022-03-30 20:07:45,021 DEBUG client.py Запущены процессы +2022-03-30 20:08:44,109 DEBUG client.py Сформирован словарь сообщения: {'action': 'message', 'sender': 'user1', 'to': 'user3', 'time': 1648660124.1099737, 'mess_text': 'Hi user3!'} +2022-03-30 20:08:44,109 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:08:44,109 INFO client.py Отправлено сообщение для пользователя user3 +2022-03-30 20:08:44,109 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:08:44,250 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:08:44,250 INFO client.py Получено сообщение от пользователя user1: Hi user3! +2022-03-30 20:09:27,235 DEBUG client.py Сформирован словарь сообщения: {'action': 'message', 'sender': 'user3', 'to': 'user1', 'time': 1648660167.235907, 'mess_text': 'Hellow'} +2022-03-30 20:09:27,235 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:09:27,235 INFO client.py Отправлено сообщение для пользователя user1 +2022-03-30 20:09:27,251 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:09:27,361 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:09:27,361 INFO client.py Получено сообщение от пользователя user3: Hellow +2022-03-30 20:10:03,216 DEBUG client.py Сформирован словарь сообщения: {'action': 'message', 'sender': 'user3', 'to': 'user2', 'time': 1648660203.2163723, 'mess_text': 'Привет user2!!!'} +2022-03-30 20:10:03,216 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:03,216 INFO client.py Отправлено сообщение для пользователя user2 +2022-03-30 20:10:03,216 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:10:03,373 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:03,373 INFO client.py Получено сообщение от пользователя user3: Привет user2!!! +2022-03-30 20:10:37,153 DEBUG client.py Сформирован словарь сообщения: {'action': 'message', 'sender': 'user2', 'to': 'user3', 'time': 1648660237.153234, 'mess_text': 'Привет, привет.'} +2022-03-30 20:10:37,153 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:37,153 INFO client.py Отправлено сообщение для пользователя user3 +2022-03-30 20:10:37,153 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:10:37,357 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:37,357 INFO client.py Получено сообщение от пользователя user2: Привет, привет. +2022-03-30 20:11:12,976 DEBUG client.py Сформирован словарь сообщения: {'action': 'message', 'sender': 'user2', 'to': 'user1', 'time': 1648660272.9766989, 'mess_text': '!!!!!'} +2022-03-30 20:11:12,992 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:11:12,992 INFO client.py Отправлено сообщение для пользователя user1 +2022-03-30 20:11:12,992 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:11:13,383 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:11:13,383 INFO client.py Получено сообщение от пользователя user2: !!!!! +2022-03-30 20:12:07,738 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:07,738 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:12:07,738 INFO client.py Завершение работы по команде пользователя. +2022-03-30 20:12:08,191 CRITICAL client.py Потеряно соединение с сервером. +2022-03-30 20:12:08,191 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:08,253 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:43,414 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:43,414 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:12:43,414 INFO client.py Завершение работы по команде пользователя. +2022-03-30 20:12:43,695 CRITICAL client.py Потеряно соединение с сервером. +2022-03-30 20:12:43,695 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:50,704 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:50,720 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:12:50,720 INFO client.py Завершение работы по команде пользователя. +2022-03-30 20:12:50,767 CRITICAL client.py Потеряно соединение с сервером. +2022-03-30 20:12:50,767 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:51,235 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . diff --git a/Lisson_8/log/client_log_config.py b/Lisson_8/log/client_log_config.py new file mode 100644 index 0000000..a83472c --- /dev/null +++ b/Lisson_8/log/client_log_config.py @@ -0,0 +1,40 @@ +"""Настройка логгера для client""" + +import logging +import os +import sys +sys.path.append('../') +from common.variables import LEVEL_LOGGING + +# Задаем формат логов +CLIENT_FORMATTER = logging.Formatter('%(asctime)s %(levelname)-8s %(filename)-18s %(message)s') + +# Подготовка имени log-файла +PATH = os.path.dirname(os.path.abspath(__file__)) +PATH = os.path.join(PATH, 'client.log') + +# Создаем обработчики логов +STREAM_HANDLER = logging.StreamHandler(sys.stderr) +STREAM_HANDLER.setFormatter(CLIENT_FORMATTER) +STREAM_HANDLER.setLevel(logging.ERROR) + +FILE_HANDLER = logging.FileHandler(PATH, encoding='utf-8') +FILE_HANDLER.setFormatter(CLIENT_FORMATTER) + +# Создаем регистратор +LOGGER = logging.getLogger('client') +LOGGER.addHandler(STREAM_HANDLER) +LOGGER.addHandler(FILE_HANDLER) +LOGGER.setLevel(LEVEL_LOGGING) + + +# Проверка работы логирования +def main(): + LOGGER.info('Просто информация') + LOGGER.debug('Отладочная информация') + LOGGER.error('Сообщение об ощибке') + LOGGER.critical('Критическая ошибка') + + +if __name__ == '__main__': + main() diff --git a/Lisson_8/log/server.log b/Lisson_8/log/server.log new file mode 100644 index 0000000..9efd310 --- /dev/null +++ b/Lisson_8/log/server.log @@ -0,0 +1,56 @@ +2022-03-30 20:07:44,272 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,272 INFO server.py Запущен server !Порт для подключений: 7777,Адрес с которого принимаются подключения: .Если адрес не указан, принимаются соединения с любых адресов. +2022-03-30 20:07:44,460 DEBUG server.py Установлено соединение с клиентом: ('127.0.0.1', 49971) +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,460 DEBUG server.py Разбор сообщения от клиента: {'action': 'presence', 'time': 1648660064.4602149, 'user': {'account_name': 'user3'}} +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,460 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,491 DEBUG server.py Установлено соединение с клиентом: ('127.0.0.1', 49972) +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,491 DEBUG server.py Разбор сообщения от клиента: {'action': 'presence', 'time': 1648660064.4914148, 'user': {'account_name': 'user1'}} +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:44,491 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:07:44,507 DEBUG server.py Установлено соединение с клиентом: ('127.0.0.1', 49973) +2022-03-30 20:07:45,021 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:45,021 DEBUG server.py Разбор сообщения от клиента: {'action': 'presence', 'time': 1648660064.507015, 'user': {'account_name': 'user2'}} +2022-03-30 20:07:45,021 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:07:45,021 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:08:44,250 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:08:44,250 DEBUG server.py Разбор сообщения от клиента: {'action': 'message', 'sender': 'user1', 'to': 'user3', 'time': 1648660124.1099737, 'mess_text': 'Hi user3!'} +2022-03-30 20:08:44,250 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:08:44,250 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:08:44,250 INFO server.py Отправлено сообщение пользователю user3 от пользователя user1. +2022-03-30 20:08:44,250 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:09:27,361 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:09:27,361 DEBUG server.py Разбор сообщения от клиента: {'action': 'message', 'sender': 'user3', 'to': 'user1', 'time': 1648660167.235907, 'mess_text': 'Hellow'} +2022-03-30 20:09:27,361 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:09:27,361 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:09:27,361 INFO server.py Отправлено сообщение пользователю user1 от пользователя user3. +2022-03-30 20:09:27,361 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:10:03,373 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:03,373 DEBUG server.py Разбор сообщения от клиента: {'action': 'message', 'sender': 'user3', 'to': 'user2', 'time': 1648660203.2163723, 'mess_text': 'Привет user2!!!'} +2022-03-30 20:10:03,373 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:10:03,373 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:03,373 INFO server.py Отправлено сообщение пользователю user2 от пользователя user3. +2022-03-30 20:10:03,373 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:10:37,357 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:37,357 DEBUG server.py Разбор сообщения от клиента: {'action': 'message', 'sender': 'user2', 'to': 'user3', 'time': 1648660237.153234, 'mess_text': 'Привет, привет.'} +2022-03-30 20:10:37,357 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:10:37,357 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:10:37,357 INFO server.py Отправлено сообщение пользователю user3 от пользователя user2. +2022-03-30 20:10:37,357 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:11:13,383 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:11:13,383 DEBUG server.py Разбор сообщения от клиента: {'action': 'message', 'sender': 'user2', 'to': 'user1', 'time': 1648660272.9766989, 'mess_text': '!!!!!'} +2022-03-30 20:11:13,383 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:11:13,383 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:11:13,383 INFO server.py Отправлено сообщение пользователю user1 от пользователя user2. +2022-03-30 20:11:13,383 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:08,191 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:12:08,191 DEBUG server.py Разбор сообщения от клиента: {'action': 'exit', 'time': 1648660327.7386403, 'account_name': 'user3'} +2022-03-30 20:12:08,191 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:43,695 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:12:43,695 DEBUG server.py Разбор сообщения от клиента: {'action': 'exit', 'time': 1648660363.4147696, 'account_name': 'user2'} +2022-03-30 20:12:43,695 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . +2022-03-30 20:12:50,767 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: . Вызов из функции: . +2022-03-30 20:12:50,767 DEBUG server.py Разбор сообщения от клиента: {'action': 'exit', 'time': 1648660370.7048018, 'account_name': 'user1'} +2022-03-30 20:12:50,767 DEBUG decors_def.py Была вызвана функция: . Вызов из модуля: <__main__>. Вызов из функции: . diff --git a/Lisson_8/log/server_log_config.py b/Lisson_8/log/server_log_config.py new file mode 100644 index 0000000..208b4e9 --- /dev/null +++ b/Lisson_8/log/server_log_config.py @@ -0,0 +1,51 @@ +"""Настройка логгера для server""" + +import logging +from logging.handlers import RotatingFileHandler, TimedRotatingFileHandler +import os +import sys +sys.path.append('../') +from common.variables import LEVEL_LOGGING + +# Задаем формат логов +SERVER_FORMATTER = logging.Formatter('%(asctime)s %(levelname)-8s %(filename)-18s %(message)s') + +# Подготовка имени лог файла + +PATH = os.path.dirname(os.path.abspath(__file__)) +PATH = os.path.join(PATH, 'server.log') + +# Создаем обработчики логов +STREAM_HANDLER = logging.StreamHandler(sys.stderr) +STREAM_HANDLER.setFormatter(SERVER_FORMATTER) +STREAM_HANDLER.setLevel(logging.ERROR) + +# FILE_HANDLER = logging.FileHandler(PATH, encoding='utf-8') + +# Ротация по длине файла равной 1500 байт +# FILE_HANDLER = RotatingFileHandler(PATH, encoding='utf-8', maxBytes=1500, backupCount=5) + +# ротация с интервалом 1 минута +# FILE_HANDLER = TimedRotatingFileHandler(PATH, encoding='utf-8', when='M', interval=1, backupCount=5) + +# ротация логфайла раз в сутки в полночь +FILE_HANDLER = TimedRotatingFileHandler(PATH, encoding='utf-8', when='H', interval=24, backupCount=5, atTime=None) +FILE_HANDLER.setFormatter(SERVER_FORMATTER) + +# Создаем регистратор +LOGGER = logging.getLogger('server') +LOGGER.addHandler(STREAM_HANDLER) +LOGGER.addHandler(FILE_HANDLER) +LOGGER.setLevel(LEVEL_LOGGING) + + +# Проверка работы логирования +def main(): + LOGGER.info('Просто информация') + LOGGER.debug('Отладочная информация') + LOGGER.error('Сообщение об ощибке') + LOGGER.critical('Критическая ошибка') + + +if __name__ == '__main__': + main() diff --git a/Lisson_8/manual.txt b/Lisson_8/manual.txt new file mode 100644 index 0000000..6563135 --- /dev/null +++ b/Lisson_8/manual.txt @@ -0,0 +1,32 @@ +Консольный мессенджер. + +1. Описание модулей: + а. common - пакет с утилитами приёма передачи данных и глобальными переменными, используемыми в проекте. + б. logs - пакет с конфигурацией логирования и сами логи работы. + в. unit_tests - автотесты для проверки работоспособности функций проекта + г. client.py - основной клиентский модуль. + д. decos.py - функция декоратор для логирования вызовов функций. + е. errors.py - описание классов исключений, используемые в проекте. + ё. launcher.py - вспомогательная утилита для одновременного запуска сервера и нескольких клиентов. + ж. server.py - основной серверный модуль. + +2. Клиентский модуль - client.py + Модуль поддерживает отправку сообщений адресатам, одновременно с этим приём новых сообщений. + Поддерживаются опции командной строки: + а. Адрес сервера. Позволяет указать адрес сервера для подключения. По умолчанию Localhost. + б. Порт сервера. Позволяет указать порт, по которому будет производиться подключение. По умолчанию 7777 + в. -n или --name. Имя пользователя в системе. По умолчанию не задан. Если не указать данный параметр, программа + при запуске запросит имя пользователя для авторизации в системе. + После запуска приложения будет произведена попытка установить соединение с сервером. + В случае удачи будет выведена справка по внутренним командам приложения: + а. message. Отправить сообщение. После ввода команды приложение запросит имя получателя и само сообщение. + б. help. Повторно выводит справку о командах приложения. + в. exit. Завершает работы приложения. + +3. Серверный модуль - server.py + Модуль обеспечивает пересылку сообщений поступаемых от клиентов адресатам. Сообщения обрабатываются на сервере + и отправляются только адресату. + Поддерживаются опции командной строки: + a. Номер порта. Номер порта на котором сервер будет принимать входящие подключения. По умолчанию 777 + б. Адрес. Адрес с которого сервер будет принимать подключения. По умолчанию слушаются все адреса. + После запуска сервера никакие дополнительные действия не требуются. \ No newline at end of file diff --git a/Lisson_8/server.py b/Lisson_8/server.py new file mode 100644 index 0000000..f66f645 --- /dev/null +++ b/Lisson_8/server.py @@ -0,0 +1,185 @@ +"""Программа-сервер""" + +import argparse +import json +import select +from socket import socket, AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR +import sys +from common.variables import DEFAULT_PORT, MAX_CONNECTIONS, ACTION, PRESENCE, TIME, USER, ACCOUNT_NAME, \ + RESPONSE, ERROR, RESPONDEFAULT_IP_ADDRESSSE, MESSAGE, MESSAGE_TEXT, SENDER, DESTINATION, RESPONSE_200, RESPONSE_400, EXIT +from common.utils import get_message, send_message +import logging +import log.server_log_config +from decorators.decors_def import log + + +# Инициализируем серверный логер +SERVER_LOGGER = logging.getLogger('server') + + +# Обработчик сообщений +@log +def process_client_message(message, messages_list, client, clients, names): + """ + Обработчик сообщений от клиентов, принимает словарь - сообщение от клинта, + проверяет корректность, отправляет словарь-ответ для клиента с результатом приёма. + """ + SERVER_LOGGER.debug(f'Разбор сообщения от клиента: {message}') + + # Если это сообщение о присутствии, принимаем и отвечаем, если успех + if ACTION in message and message[ACTION] == PRESENCE and TIME in message \ + and USER in message: + # Если такой пользователь ещё не зарегистрирован, + # регистрируем, иначе отправляем ответ и завершаем соединение. + if message[USER][ACCOUNT_NAME] not in names.keys(): + names[message[USER][ACCOUNT_NAME]] = client + send_message(client, RESPONSE_200) + else: + response = RESPONSE_400 + response[ERROR] = 'Имя пользователя уже занято.' + send_message(client, response) + clients.remove(client) + client.close() + return + + # Если это сообщение, то добавляем его в очередь сообщений. Ответ не требуется. + elif ACTION in message and message[ACTION] == MESSAGE and \ + DESTINATION in message and TIME in message \ + and SENDER in message and MESSAGE_TEXT in message: + messages_list.append(message) + return + + # Если клиент выходит + elif ACTION in message and message[ACTION] == EXIT and ACCOUNT_NAME in message: + clients.remove(names[message[ACCOUNT_NAME]]) + names[message[ACCOUNT_NAME]].close() + del names[message[ACCOUNT_NAME]] + return + + # Иначе отдаём Bad request + else: + response = RESPONSE_400 + response[ERROR] = 'Запрос некорректен.' + send_message(client, response) + return + + +@log +def process_message(message, names, listen_socks): + """ + Функция адресной отправки сообщения определённому клиенту. Принимает словарь сообщение, + список зарегистрированых пользователей и слушающие сокеты. Ничего не возвращает. + """ + if message[DESTINATION] in names and names[message[DESTINATION]] in listen_socks: + send_message(names[message[DESTINATION]], message) + SERVER_LOGGER.info(f'Отправлено сообщение пользователю {message[DESTINATION]} ' + f'от пользователя {message[SENDER]}.') + elif message[DESTINATION] in names and names[message[DESTINATION]] not in listen_socks: + raise ConnectionError + else: + SERVER_LOGGER.error( + f'Пользователь {message[DESTINATION]} не зарегистрирован на сервере, ' + f'отправка сообщения невозможна.') + + +@log +def arg_parser(): + """Парсер аргументов коммандной строки""" + parser = argparse.ArgumentParser() + parser.add_argument( + '-p', + default=DEFAULT_PORT, + type=int, + nargs='?', + help='port' + ) + parser.add_argument( + '-a', + default='', + nargs='?', + help='ip address' + ) + + namespace = parser.parse_args(sys.argv[1:]) + listen_address = namespace.a + listen_port = namespace.p + + # Проверка адреса порта + if not 1023 < listen_port < 65536: + SERVER_LOGGER.critical( + f'Попытка запуска сервера с некоректным адрессом порта ' + f'{listen_port}. Допустимы адреса с 1024 по 65535.' + ) + sys.exit(1) + + return listen_address, listen_port + + +def main(): + """Загрузка параметров командной строки, если нет параметров, то задаём значения по умоланию""" + listen_address, listen_port = arg_parser() + + SERVER_LOGGER.info( + f'Запущен server !' + f'Порт для подключений: {listen_port},' + f'Адрес с которого принимаются подключения: {listen_address}.' + f'Если адрес не указан, принимаются соединения с любых адресов.' + ) + + # Готовим сокет + serv_sock = socket(AF_INET, SOCK_STREAM) + serv_sock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) + serv_sock.bind((listen_address, listen_port)) + serv_sock.settimeout(0.5) + + clients = [] + messages = [] + names = dict() # {client_name: client_socket} + + # Слушаем порт + serv_sock.listen(MAX_CONNECTIONS) + + # Основной цикл Сервера + while True: + try: + # Ждём подключения, если таймаут вышел, ловим исключение. + client, client_address = serv_sock.accept() + except OSError as err: + pass + else: + SERVER_LOGGER.debug(f'Установлено соединение с клиентом: {client_address}') + clients.append(client) + finally: + wait = 0 + received_data_lst = [] + send_data_lst = [] + err_lst = [] + # Проверяем на наличие ждущих клиентов + try: + if clients: + received_data_lst, send_data_lst, err_lst = select.select(clients, clients, [], wait) + except OSError: + pass + + # Принимаем сообщения и сохраняем в словаре, если ошибка, удаляем клиента + if received_data_lst: + for client_with_message in received_data_lst: + try: + process_client_message(get_message(client_with_message), messages, client_with_message, clients, names) + except: + SERVER_LOGGER.info(f'Клиент {client_with_message.getpeername()} отключился от сервера.') + clients.remove(client_with_message) + + # Если есть сообщения, обрабатываем каждое. + for el in messages: + try: + process_message(el, names, send_data_lst) + except Exception: + SERVER_LOGGER.info(f'Связь с клиентом: {el[DESTINATION]} была потеряна') + clients.remove(names[el[DESTINATION]]) + del names[el[DESTINATION]] + messages.clear() + + +if __name__ == '__main__': + main()