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 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 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 test_send_message(self): """ Тестируем корректность работы фукции отправки, создадим тестовый сокет и проверим корректность отправки словаря :return: """ # экземпляр тестового словаря, хранит собственно тестовый словарь test_socket = TestSocket(self.test_dict_send) # вызов тестируемой функции, результаты будут сохранены в тестовом сокете send_message(test_socket, self.test_dict_send) # проверка корретности кодирования словаря. # сравниваем результат довренного кодирования и результат от тестируемой функции self.assertEqual(test_socket.encoded_message, test_socket.receved_message) # дополнительно, проверим генерацию исключения, при не словаре на входе. self.assertRaises(NonDictInputError, send_message, test_socket, 1111)
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 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 process_message(self, message, listen_socks): """ Функция адресной отправки сообщения определённому клиенту. Принимает словарь сообщение, список зарегистрированых пользователей и слушающие сокеты. Ничего не возвращает. :param message: :param listen_socks: :return: """ if message[DESTINATION] in self.names and self.names[ message[DESTINATION]] in listen_socks: send_message(self.names[message[DESTINATION]], message) SERVER_LOGGER.info( f'Отправлено сообщение пользователю {message[DESTINATION]} ' f'от пользователя {message[SENDER]}.') elif message[DESTINATION] in self.names and self.names[ message[DESTINATION]] not in listen_socks: raise ConnectionError else: SERVER_LOGGER.error( f'Пользователь {message[DESTINATION]} не зарегистрирован на сервере, ' f'отправка сообщения невозможна.')
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 process_client_message(self, message, client): """ Обработчик сообщений от клиентов, принимает словарь - сообщение от клиента, проверяет корректность, отправляет словарь-ответ в случае необходимости. :param client_socket: :param messages_list: :param client: :param clients_list: :param names: :return: """ global new_connection 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 self.names.keys(): self.names[message[USER][ACCOUNT_NAME]] = client # подготовка данных client_ip, client_port = client.getpeername() # отправка в базу данных self.database.user_login(message[USER][ACCOUNT_NAME], client_ip, client_port) send_message(client, RESPONSE_200) with conflag_lock: new_connection = True else: response = RESPONSE_400 response[ERROR] = 'Имя пользователя уже занято.' send_message(client, response) self.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 \ and self.names[message[SENDER]] == client: self.messages.append(message) self.database.process_message(message[SENDER], message[DESTINATION]) return # Если клиент выходит elif ACTION in message and message[ACTION] == EXIT and ACCOUNT_NAME in message \ and self.names[message[ACCOUNT_NAME]] == client: # отправка в базу данных self.database.user_logout(message[ACCOUNT_NAME]) SERVER_LOGGER.info( f'Клиент {message[ACCOUNT_NAME]} корректно отключился от сервера.' ) self.clients.remove(self.names[message[ACCOUNT_NAME]]) self.names[message[ACCOUNT_NAME]].close() del self.names[message[ACCOUNT_NAME]] with conflag_lock: new_connection = True return # Если это запрос контакт-листа elif ACTION in message and message[ACTION] == GET_CONTACTS and USER in message and \ self.names[message[USER]] == client: response = RESPONSE_202 response[LIST_INFO] = self.database.get_contacts(message[USER]) send_message(client, response) # Если это добавление контакта elif ACTION in message and message[ACTION] == ADD_CONTACT and ACCOUNT_NAME in message and USER in message \ and self.names[message[USER]] == client: self.database.add_contact(message[USER], message[ACCOUNT_NAME]) send_message(client, RESPONSE_200) # Если это удаление контакта elif ACTION in message and message[ACTION] == REMOVE_CONTACT and ACCOUNT_NAME in message and USER in message \ and self.names[message[USER]] == client: self.database.remove_contact(message[USER], message[ACCOUNT_NAME]) send_message(client, RESPONSE_200) # Если это запрос известных пользователей elif ACTION in message and message[ACTION] == USERS_REQUEST and ACCOUNT_NAME in message \ and self.names[message[ACCOUNT_NAME]] == client: response = RESPONSE_202 response[LIST_INFO] = [ user[0] for user in self.database.users_list() ] send_message(client, response) # Иначе отдаём Bad request else: response = RESPONSE_400 response[ERROR] = 'Запрос некорректен.' send_message(client, response) return