def user_list_request(sock, username): CLIENT_LOGGER.debug(f'Запрос списка известных пользователей {username}') req = { ACTION: USERS_REQUEST, TIME: time.time(), ACCOUNT_NAME: username } send_message(sock, req) ans = get_message(sock) if RESPONSE in ans and ans[RESPONSE] == 202: return ans[LIST_INFO] else: raise ServerError
def process_response_answer(message): """ Функция разбирает ответ сервера на сообщение о присутствии, возращает 200 если все ОК или генерирует исключение при ошибке :param message: проверяется код ответа от сервера :return: """ 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 remove_contact(sock, username, contact): CLIENT_LOGGER.debug(f'Создание контакта {contact}') req = { ACTION: REMOVE_CONTACT, TIME: time.time(), USER: username, ACCOUNT_NAME: contact } send_message(sock, req) ans = get_message(sock) if RESPONSE in ans and ans[RESPONSE] == 200: pass else: raise ServerError('Ошибка удаления клиента') print('Удачное удаление')
def create_new_presence(account_name='Guest'): """ Функция генерирует запрос о присутствии клиента :param account_name: Guest - имя аккаунта по умолчанию :return: out: возвращает сгенерированный словарь словарь """ # {'action': 'presence', 'time': 1573760672.167031, 'user': {'account_name': 'Guest'}} out = { ACTION: PRESENCE, TIME: time.time(), USER: { ACCOUNT_NAME: account_name } } CLIENT_LOGGER.debug(f'Сформировано {PRESENCE} сообщение для пользователя {account_name}') return out
def database_load(sock, database, username): # Загружаем список известных пользователей try: users_list = user_list_request(sock, username) except ServerError: CLIENT_LOGGER.error('Ошибка запроса списка известных пользователей.') else: database.add_users(users_list) # Загружаем список контактов try: contacts_list = contacts_list_request(sock, username) except ServerError: CLIENT_LOGGER.error('Ошибка запроса списка контактов.') else: for contact in contacts_list: database.add_contact(contact)
def run(self): """Функция взаимодействия с пользователем, запрашивает команды, отправляет сообщения""" self.print_help() while True: command = input('Введите команду: ') # Если отправка сообщения - соответствующий метод if command == 'message': self.create_message() # Вывод помощи elif command == 'help': self.print_help() # Выход. Отправляем сообщение серверу о выходе. elif command == 'exit': with sock_lock: try: send_message(self.sock, self.create_exit_message()) except: pass print('Завершение соединения.') CLIENT_LOGGER.info('Завершение работы по команде пользователя.') # Задержка неоходима, чтобы успело уйти сообщение о выходе time.sleep(0.5) break # Список контактов elif command == 'contacts': with database_lock: contacts_list = self.database.get_contacts() for contact in contacts_list: print(contact) # Редактирование контактов elif command == 'edit': self.edit_contacts() # история сообщений. elif command == 'history': self.print_history() else: print('Команда не распознана, попробойте снова. help - вывести поддерживаемые команды.')
def arg_parser(): """Создаём парсер аргументов коммандной строки и читаем параметры, возвращаем 3 параметра""" parser = argparse.ArgumentParser() parser.add_argument('addr', default=DEFAULT_IP_ADDRESS, nargs='?') parser.add_argument('port', default=DEFAULT_PORT, type=int, nargs='?') parser.add_argument('-n', '--name', default=None, nargs='?') namespace = parser.parse_args(sys.argv[1:]) server_address = namespace.addr server_port = namespace.port client_name = namespace.name # проверим подходящий номер порта if not 1023 < server_port < 65536: CLIENT_LOGGER.critical( f'Попытка запуска клиента с неподходящим номером порта: {server_port}. ' f'Допустимы адреса с 1024 до 65535. Клиент завершается.') exit(1) return server_address, server_port, client_name
def edit_contacts(self): ans = input('Для удаления введите del, для добавления add: ') if ans == 'del': edit = input('Введите имя удаляемного контакта: ') with database_lock: if self.database.check_contact(edit): self.database.del_contact(edit) else: CLIENT_LOGGER.error('Попытка удаления несуществующего контакта.') elif ans == 'add': # Проверка на возможность такого контакта edit = input('Введите имя создаваемого контакта: ') if self.database.check_user(edit): with database_lock: self.database.add_contact(edit) with sock_lock: try: add_contact(self.sock, self.account_name, edit) except ServerError: CLIENT_LOGGER.error('Не удалось отправить информацию на сервер.')
def create_message(self): """ Функция запрашивает кому отправить сообщение и само сообщение, и отправляет полученные данные на сервер :param sock: :param account_name: :return: """ to = input('Введите получателя сообщения: ') message = input('Введите сообщение для отправки: ') # Проверим, что получатель существует with database_lock: if not self.database.check_user(to): CLIENT_LOGGER.error(f'Попытка отправить сообщение незарегистрированому получателю: {to}') return message_dict = { ACTION: MESSAGE, SENDER: self.account_name, DESTINATION: to, TIME: time.time(), MESSAGE_TEXT: message } CLIENT_LOGGER.debug(f'Сформирован словарь сообщения: {message_dict}') # Сохраняем сообщения для истории with database_lock: self.database.save_message(self.account_name, to, message) # Необходимо дождаться освобождения сокета для отправки сообщения with sock_lock: try: send_message(self.sock, message_dict) CLIENT_LOGGER.info(f'Отправлено сообщение для пользователя {to}') except OSError as err: if err.errno: CLIENT_LOGGER.critical('Потеряно соединение с сервером.') exit(1) else: CLIENT_LOGGER.error('Не удалось передать сообщение. Таймаут соединения')
def contacts_list_request(sock, name): CLIENT_LOGGER.debug(f'Запрос контакт листа для пользователся {name}') req = { ACTION: GET_CONTACTS, TIME: time.time(), USER: name } CLIENT_LOGGER.debug(f'Сформирован запрос {req}') send_message(sock, req) ans = get_message(sock) CLIENT_LOGGER.debug(f'Получен ответ {ans}') if RESPONSE in ans and ans[RESPONSE] == 202: return ans[LIST_INFO] else: raise ServerError
def main(): """Сообщаем о запуске""" print('Консольный месседжер. Клиентский модуль.') # Загружаем параметы коммандной строки # client.py 192.168.1.2 8079 ... server_address, server_port, client_name = arg_parser() # Если имя пользователя не было задано, необходимо запросить пользователя. if not client_name: client_name = input('Введите имя пользователя: ') else: print(f'Клиентский модуль запущен с именем: {client_name}') CLIENT_LOGGER.info( f'Запущен клиент с парамертами: адрес сервера: {server_address}, ' f'порт: {server_port}, имя пользователя: {client_name}') # Инициализация сокета и сообщение серверу о нашем появлении try: client_socket = socket(AF_INET, SOCK_STREAM) # Таймаут 1 секунда, необходим для освобождения сокета. client_socket.settimeout(1) client_socket.connect((server_address, server_port)) send_message(client_socket, create_new_presence(client_name)) answer = process_response_answer(get_message(client_socket)) CLIENT_LOGGER.info(f'Установлено соединение с сервером. Ответ сервера: {answer}') print(f'Установлено соединение с сервером.') except json.JSONDecodeError: CLIENT_LOGGER.error('Не удалось декодировать полученную Json строку.') exit(1) except ServerError as error: CLIENT_LOGGER.error(f'При установке соединения сервер вернул ошибку: {error.text}') exit(1) except ReqFieldMissingError as missing_error: CLIENT_LOGGER.error(f'В ответе сервера отсутствует необходимое поле {missing_error.missing_field}') exit(1) except (ConnectionRefusedError, ConnectionError): CLIENT_LOGGER.critical( f'Не удалось подключиться к серверу {server_address}:{server_port}, ' f'конечный компьютер отверг запрос на подключение.') exit(1) else: # Инициализация БД database = ClientDatabase(client_name) database_load(client_socket, database, client_name) # Если соединение с сервером установлено корректно, # запускаем поток взаимодействия с пользователем module_sender = ClientSender(client_name, client_socket, database) module_sender.daemon = True module_sender.start() CLIENT_LOGGER.debug('Запущены процессы') # затем запускаем поток - приёмник сообщений. module_receiver = ClientReader(client_name, client_socket, database) module_receiver.daemon = True module_receiver.start() # Watchdog основной цикл, если один из потоков завершён, # то значит или потеряно соединение или пользователь # ввёл exit. Поскольку все события обработываются в потоках, # достаточно просто завершить цикл. while True: time.sleep(1) if module_receiver.is_alive() and module_sender.is_alive(): continue break
def run(self): """Функция - обработчик сообщений других пользователей, поступающих с сервера""" while True: # Отдыхаем секунду и снова пробуем захватить сокет. # если не сделать тут задержку, то второй поток может достаточно долго ждать освобождения сокета. time.sleep(1) with sock_lock: try: message = get_message(self.sock) # Принято некорректное сообщение except IncorrectDataReceivedError: CLIENT_LOGGER.error(f'Не удалось декодировать полученное сообщение.') # Вышел таймаут соединения если errno = None, иначе обрыв соединения. except OSError as err: if err.errno: CLIENT_LOGGER.critical(f'Потеряно соединение с сервером.') break # Проблемы с соединением except (ConnectionError, ConnectionAbortedError, ConnectionResetError, json.JSONDecodeError): CLIENT_LOGGER.critical(f'Потеряно соединение с сервером.') break # Если пакет корретно получен выводим в консоль и записываем в базу. else: if ACTION in message and message[ACTION] == MESSAGE and SENDER in message and DESTINATION in message \ and MESSAGE_TEXT in message and message[DESTINATION] == self.account_name: print(f'\nПолучено сообщение от пользователя {message[SENDER]}:\n{message[MESSAGE_TEXT]}') # Захватываем работу с базой данных и сохраняем в неё сообщение with database_lock: try: self.database.save_message(message[SENDER], self.account_name, message[MESSAGE_TEXT]) except: CLIENT_LOGGER.error('Ошибка взаимодействия с базой данных') CLIENT_LOGGER.info( f'Получено сообщение от пользователя {message[SENDER]}:\n{message[MESSAGE_TEXT]}') else: CLIENT_LOGGER.error(f'Получено некорректное сообщение с сервера: {message}')