def test_get_message(self): """ Тест функции приёма сообщения :return: """ test_sock_ok = TestSocket(self.test_dict_recv_ok) test_sock_err = TestSocket(self.test_dict_recv_err) # тест корректной расшифровки корректного словаря self.assertEqual(get_message(test_sock_ok), self.test_dict_recv_ok) # тест корректной расшифровки ошибочного словаря self.assertEqual(get_message(test_sock_err), self.test_dict_recv_err)
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 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 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 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}')
def run(self): """Метод основной цикл потока.""" # Инициализация Сокета self.init_socket() # Основной цикл программы сервера while self.running: # Ждём подключения, если таймаут вышел, ловим исключение. try: client, client_address = self.sock.accept() except OSError: pass else: logger.info(f'Установлено соедение с ПК {client_address}') client.settimeout(5) self.clients.append(client) recv_data_lst = [] send_data_lst = [] err_lst = [] # Проверяем на наличие ждущих клиентов try: if self.clients: recv_data_lst, self.listen_sockets, self.error_sockets = select.select( self.clients, self.clients, [], 0) except OSError as err: logger.error(f'Ошибка работы с сокетами: {err.errno}') # принимаем сообщения и если ошибка, исключаем клиента. if recv_data_lst: for client_with_message in recv_data_lst: try: self.process_client_message( get_message(client_with_message), client_with_message) except (OSError, json.JSONDecodeError, TypeError) as err: logger.debug(f'Getting data from client exception.', exc_info=err) self.remove_client(client_with_message)
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): # Инициализация Сокета global new_connection self.init_socket() # Основной цикл программы сервера while True: # Ждём подключения, если таймаут вышел, ловим исключение. try: client_socket, client_address = self.sock.accept() except OSError: pass else: SERVER_LOGGER.info( f'Установлено соедение с ПК {client_address}') self.clients.append(client_socket) recv_data_lst = [] send_data_lst = [] err_lst = [] # Проверяем на наличие ждущих клиентов try: if self.clients: recv_data_lst, send_data_lst, err_lst = select( self.clients, self.clients, [], 0) except OSError as err: SERVER_LOGGER.error(f'Ошибка работы с сокетами: {err}') # принимаем сообщения и если ошибка, исключаем клиента. if recv_data_lst: for client_with_message in recv_data_lst: try: self.process_client_message( get_message(client_with_message), client_with_message) except OSError: # Ищем клиента в словаре клиентов и удаляем его из него # и базы подключённых SERVER_LOGGER.info( f'Клиент {client_with_message.getpeername()} ' f'отключился от сервера.') for name in self.names: if self.names[name] == client_with_message: self.database.user_logout(name) del self.names[name] break self.clients.remove(client_with_message) with conflag_lock: new_connection = True # Если есть сообщения, обрабатываем каждое. for message in self.messages: try: self.process_message(message, send_data_lst) except (ConnectionAbortedError, ConnectionError, ConnectionResetError, ConnectionRefusedError): SERVER_LOGGER.info( f'Связь с клиентом с именем {message[DESTINATION]} была потеряна' ) self.clients.remove( self.names[message[DESTINATION]]) self.database.user_logout(message[DESTINATION]) del self.names[message[DESTINATION]] with conflag_lock: new_connection = True self.messages.clear()
def autorize_user(self, message, sock): """Метод реализующий авторизцию пользователей.""" # Если имя пользователя уже занято то возвращаем 400 logger.debug(f'Start auth process for {message[USER]}') if message[USER][ACCOUNT_NAME] in self.names.keys(): response = RESPONSE_400 response[ERROR] = 'Имя пользователя уже занято.' try: logger.debug(f'Username busy, sending {response}') send_message(sock, response) except OSError: logger.debug('OS Error') pass self.clients.remove(sock) sock.close() # Проверяем что пользователь зарегистрирован на сервере. elif not self.database.check_user(message[USER][ACCOUNT_NAME]): response = RESPONSE_400 response[ERROR] = 'Пользователь не зарегистрирован.' try: logger.debug(f'Unknown username, sending {response}') send_message(sock, response) except OSError: pass self.clients.remove(sock) sock.close() else: logger.debug('Correct username, starting passwd check.') # Иначе отвечаем 511 и проводим процедуру авторизации # Словарь - заготовка message_auth = RESPONSE_511 # Набор байтов в hex представлении random_str = binascii.hexlify(os.urandom(64)) # В словарь байты нельзя, декодируем (json.dumps -> TypeError) message_auth[DATA] = random_str.decode('ascii') # Создаём хэш пароля и связки с рандомной строкой, сохраняем # серверную версию ключа hash = hmac.new(self.database.get_hash(message[USER][ACCOUNT_NAME]), random_str, 'MD5') digest = hash.digest() logger.debug(f'Auth message = {message_auth}') try: # Обмен с клиентом send_message(sock, message_auth) ans = get_message(sock) except OSError as err: logger.debug('Error in auth, data:', exc_info=err) sock.close() return client_digest = binascii.a2b_base64(ans[DATA]) # Если ответ клиента корректный, то сохраняем его в список # пользователей. if RESPONSE in ans and ans[RESPONSE] == 511 and hmac.compare_digest( digest, client_digest): self.names[message[USER][ACCOUNT_NAME]] = sock client_ip, client_port = sock.getpeername() try: send_message(sock, RESPONSE_200) except OSError: self.remove_client(message[USER][ACCOUNT_NAME]) # добавляем пользователя в список активных и если у него изменился открытый ключ # сохраняем новый self.database.user_login( message[USER][ACCOUNT_NAME], client_ip, client_port, message[USER][PUBLIC_KEY]) else: response = RESPONSE_400 response[ERROR] = 'Неверный пароль.' try: send_message(sock, response) except OSError: pass self.clients.remove(sock) sock.close()