class Server(threading.Thread, metaclass=ServerMaker): port = Port() def __init__(self, listen_address, listen_port, database): # Параментры подключения self.addr = listen_address self.port = listen_port # База данных сервера self.database = database # Список подключённых клиентов. self.clients = [] # Список сообщений на отправку. self.messages = [] # Словарь содержащий сопоставленные имена и соответствующие им сокеты. self.names = dict() # Конструктор предка super().__init__() def init_socket(self): logger.info( f'Запущен сервер, порт для подключений: {self.port} , адрес с которого принимаются подключения: {self.addr}. Если адрес не указан, принимаются соединения с любых адресов.' ) # Готовим сокет transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM) transport.bind((self.addr, self.port)) transport.settimeout(0.5) # Начинаем слушать сокет. self.sock = transport self.sock.listen() def run(self): # Инициализация Сокета self.init_socket() # Основной цикл программы сервера while True: # Ждём подключения, если таймаут вышел, ловим исключение. try: client, client_address = self.sock.accept() except OSError: pass else: logger.info(f'Установлено соедение с ПК {client_address}') self.clients.append(client) recv_data_lst = [] send_data_lst = [] err_lst = [] # Проверяем на наличие ждущих клиентов try: if self.clients: recv_data_lst, send_data_lst, err_lst = select.select( self.clients, self.clients, [], 0) except OSError as err: 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): # Ищем клиента в словаре клиентов и удаляем его из него # и базы подключённых logger.info( f'Клиент {client_with_message.getpeername()} отключился от сервера.' ) 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) # Если есть сообщения, обрабатываем каждое. for message in self.messages: try: self.process_message(message, send_data_lst) except (ConnectionAbortedError, ConnectionError, ConnectionResetError, ConnectionRefusedError): logger.info( f'Связь с клиентом с именем {message[DESTINATION]} была потеряна' ) self.clients.remove(self.names[message[DESTINATION]]) self.database.user_logout(message[DESTINATION]) del self.names[message[DESTINATION]] self.messages.clear() # Функция адресной отправки сообщения определённому клиенту. Принимает словарь сообщение, список зарегистрированых # пользователей и слушающие сокеты. Ничего не возвращает. def process_message(self, message, listen_socks): if message[DESTINATION] in self.names and self.names[ message[DESTINATION]] in listen_socks: send_message(self.names[message[DESTINATION]], message) logger.info( f'Отправлено сообщение пользователю {message[DESTINATION]} от пользователя {message[SENDER]}.' ) elif message[DESTINATION] in self.names and self.names[ message[DESTINATION]] not in listen_socks: raise ConnectionError else: logger.error( f'Пользователь {message[DESTINATION]} не зарегистрирован на сервере, отправка сообщения невозможна.' ) # Обработчик сообщений от клиентов, принимает словарь - сообщение от клиента, проверяет корректность, отправляет # словарь-ответ в случае необходимости. def process_client_message(self, message, client): global new_connection 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]) 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
class Server(threading.Thread, metaclass=ServerMaker): port = Port() def __init__(self, listen_address, listen_port, database): self.addr = listen_address self.port = listen_port self.database = database self.clients = [] self.messages = [] self.names = dict() super().__init__() def init_socket(self): logger.info(f'Server is ready, port to conenct: {self.port} , ' f'connection from address: {self.addr}. ' f'If not set, allowed connections from all port.') transport = socket.socket(socket.AF_INET, socket.SOCK_STREAM) transport.bind((self.addr, self.port)) transport.settimeout(0.5) self.sock = transport self.sock.listen() def run(self): global new_connection self.init_socket() while True: try: client, client_address = self.sock.accept() except OSError: pass else: logger.info(f'Connection with PC {client_address}') self.clients.append(client) recv_data_lst = [] send_data_lst = [] err_lst = [] try: if self.clients: recv_data_lst, send_data_lst, err_lst = select.select( self.clients, self.clients, [], 0) except OSError as err: logger.error(f'socket process error: {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): logger.info( f'Client {client_with_message.getpeername()} ' f'disconnected from server.') 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): logger.info(f'Connection with {message[DESTINATION]} lsot') 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 process_message(self, message, listen_socks): if message[DESTINATION] in self.names and self.names[ message[DESTINATION]] in listen_socks: send_message(self.names[message[DESTINATION]], message) logger.info( f'Message to user {message[DESTINATION]} by user {message[SENDER]}.' ) elif message[DESTINATION] in self.names and self.names[ message[DESTINATION]] not in listen_socks: raise ConnectionError else: logger.error(f'User {message[DESTINATION]} is incorrect,' f' could not sent message)') def process_client_message(self, message, client): global new_connection logger.debug(f'Parsing user message: {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] = 'User name is alredy exists.' 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: if message[DESTINATION] in self.names: self.messages.append(message) self.database.process_message(message[SENDER], message[DESTINATION]) send_message(client, RESPONSE_200) else: response = RESPONSE_400 response[ERROR] = 'User not registered on server.' send_message(client, response) 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]) logger.info(f'user {message[ACCOUNT_NAME]} succesfully logout.') 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) else: response = RESPONSE_400 response[ERROR] = 'Request is incorrect.' send_message(client, response) return