def __init__(self, server): Thread.__init__(self) self.server = server self.serializer = Serializer() self.bind_addr = '' self.bind_port = None self.sock = None self.__stop = False
def __init__(self, server, sock, addr, number): Thread.__init__(self) self.server = server self.sock = sock self.addr = addr self.number = number self.serializer = Serializer() self.last_activity = None self.nickname = self.addr self.__stop = False
def __init__(self, nickname, server_id): self.nickname = nickname self.server_id = server_id self.serializer = Serializer() self.exit = False self.server_addr = None self.bad_servers = [] self.msg_sock = None self.threads = [] self.available_servers = []
class Read(Thread): def __init__(self, client): Thread.__init__(self) self.client = client self.__stop = False self.serializer = Serializer() def close(self): if self.client.msg_sock: self.client.msg_sock.close() def run(self): if not self.client.exit: logging.info(msg=' Enter \'exit\' to close connection.') logging.info(msg=' Please enter a message.') while not self.__stop: msg, _, _ = select.select([sys.stdin], [], [], 3) time.sleep(.5) if not msg and not self.client.msg_sock._closed: continue elif self.client.msg_sock._closed: logging.info(msg=' Sorry you got disconnected') return if msg: msg = sys.stdin.readline().strip() if msg == 'exit': self.client.exit = True self.client.disconnect_from_server() return try: self.client.msg_sock.sendall( self.serializer.serialize({ 'action': Action.MESSAGE, 'nickname': self.client.nickname, 'message': msg })) except (BrokenPipeError, OSError): continue def stop(self): self.__stop = True
class Show(Thread): def __init__(self, sock): Thread.__init__(self) self.sock = sock self.__stop = False self.serializer = Serializer() def close(self): if self.sock: self.sock.close() def run(self): while not self.__stop: try: ready, _, _ = select.select([self.sock], [], []) except (OSError, ValueError): return time.sleep(.5) if not ready: continue try: data = self.sock.recv(1024) server = self.serializer.deserialize(data) except (EOFError, ConnectionResetError): self.stop() self.close() return logging.info(msg=f' {server["nickname"]}: {server["message"]}') self.sock.close() def stop(self): self.__stop = True
class Broadcast(Thread): """ Each server should respond to broadcasts from known subnets with a message that contains following information: - Server IP - Free slots count - Server ID """ def __init__(self, server): Thread.__init__(self) self.server = server self.serializer = Serializer() self.bind_addr = '' self.bind_port = None self.sock = None self.__stop = False def close(self): if self.sock: self.sock.close() def bind_to_port(self): bind_port = None for port in Port.BROADCAST_RANGE: try: self.sock.bind((self.bind_addr, port)) bind_port = port break except OSError: continue return bind_port def set_socket(self): self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.bind_port = self.bind_to_port() if not self.bind_port: self.sock = None logging.error( msg=f' Could not find valid port to start the server') return self.sock logging.info(msg=' Wait for client broadcast...') logging.info(msg=f' Bind address : {self.bind_addr}') logging.info(msg=f' Bind port : {self.bind_port}') logging.info(msg=f' Server given port: {self.sock.getsockname()[1]}') return self.sock def run(self): if not self.set_socket(): self.stop() while not self.__stop: try: ready, _, _ = select.select([self.sock], [], [], 2) except OSError: continue if ready: data, client_addr = self.sock.recvfrom(1024) client_data = self.serializer.deserialize(data) logging.info( msg=f' Incoming client broadcast, address: {client_addr}') logging.info( msg=f' Incoming client broadcast, data: {client_data}') if client_addr[0] not in self.server.addresses: logging.info(msg=f' {client_addr} address not in list of ' f'server subnet addresses.') continue self.sock.sendto( self.serializer.serialize({ 'id': self.server.id, 'ip': self.server.host_ip, 'slots': self.server.get_slots_available(), }), client_addr) self.close() def stop(self): self.__stop = True
def __init__(self, client): Thread.__init__(self) self.client = client self.__stop = False self.serializer = Serializer()
def __init__(self, sock): Thread.__init__(self) self.sock = sock self.__stop = False self.serializer = Serializer()
class Client: def __init__(self, nickname, server_id): self.nickname = nickname self.server_id = server_id self.serializer = Serializer() self.exit = False self.server_addr = None self.bad_servers = [] self.msg_sock = None self.threads = [] self.available_servers = [] def disconnect_from_server(self): """ If server gets "disconnect" from the client, notification to all other clients should also be sent. """ self.msg_sock.sendall( self.serializer.serialize({ 'action': Action.DISCONNECT, })) self.stop() def run(self): """ If connection doesn't occur within allowed time (3 seconds), client should wait for 4 seconds and start discovery process again. """ if not self.discover(): logging.error(msg=' Failed to discover proper servers') return connected = self.connect() while not connected: logging.info(msg=' Wait 4 seconds and restart discovery process') time.sleep(4) if not self.discover(): logging.error(msg=' Failed to discover proper servers') return connected = self.connect() if connected: break self.start_threads() self.keep_alive() def keep_alive(self): """ If client gets disconnected from the server, after 4 seconds it should restart discovery process. """ while self.exit is False: time.sleep(1) if False not in self.threads_alive(): continue self.stop() if self.exit: return while True in self.threads_alive(): time.sleep(.1) logging.info(msg=' Restarting discovery in 4 seconds...') time.sleep(4) self.run() return def start_threads(self): """ When client sends a message, it should be sent to all other clients connected to the same server (no chat rooms). Each client should have a nickname attached to each message. Messages should go through the server. Client app should get messages from the user via stdin - one line for one message. Client app should display each message with nickname """ self.threads = [ Show(sock=self.msg_sock), Read(client=self), ] for thread in self.threads: thread.start() def nickname_available(self): self.msg_sock.sendall( self.serializer.serialize({ 'action': Action.NICKNAME, 'nickname': self.nickname, })) data = self.msg_sock.recv(1024) nickname_status = self.serializer.deserialize(data) if not nickname_status['available']: logging.info(msg=f' Sorry nickname \'{self.nickname}\'' f' is already taken on server \'{self.server_id}\'') self.msg_sock.close() self.exit = True self.stop() def connect(self): """ Client chooses one server by id, stops discovery process and connects (TCP/IP) to the selected server. Client app should not have any inactivity timeouts. When connecting to the server, client should wait no longer than 3 seconds for connection establishment to occur. """ max_wait = 3 connected = False for server_addr in self.available_servers: try: self.msg_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.msg_sock = socket.create_connection(server_addr, timeout=max_wait) self.nickname_available() connected = True except (socket.timeout, ConnectionRefusedError): self.bad_servers.append(server_addr) continue if connected: break if not connected: self.msg_sock = None self.available_servers = None return connected def discover(self): """ Client tries to discover all servers in all known subnets by periodic (once in 3 sec) broadcast (UDP/IP) till it finds appropriate server. """ time_period = 3 bind_addr = '' bind_port = 0 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind((bind_addr, bind_port)) sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) logging.info(msg=f' ') logging.info(msg=f' Bind address : {bind_addr}') logging.info(msg=f' Bind port : {bind_port}') logging.info(msg=f' Client port : {sock.getsockname()[1]}') for broadcast_port in Port.BROADCAST_RANGE: sock.sendto(self.serializer.serialize({'nickname': self.nickname}), ('<broadcast>', broadcast_port)) try: ready, _, _ = select.select([sock], [], [], time_period) except KeyboardInterrupt: return if not ready: continue data, server_addr = sock.recvfrom(1024) server = self.serializer.deserialize(data) # Avoid server if it # doesn't match following conditions server_conditions = [ server['id'] == self.server_id, server['slots'] > 0, server_addr not in self.bad_servers ] if server['slots'] == 0: logging.info(msg=f' {server["id"]} is full.') logging.info(msg=f' Looking for other servers...') if False not in server_conditions: self.available_servers.append(server_addr) break return self.available_servers @handle_threads_alive def threads_alive(self): pass @handle_threads_stop def stop(self): pass
class Client(Thread): def __init__(self, server, sock, addr, number): Thread.__init__(self) self.server = server self.sock = sock self.addr = addr self.number = number self.serializer = Serializer() self.last_activity = None self.nickname = self.addr self.__stop = False def stop(self): self.last_activity = None self.__stop = True def close(self): if self.sock: self.sock.close() def set_nickname(self, nickname): if self.nickname_not_set() and self.nickname_available(nickname): self.nickname = nickname self.sock.sendall(self.serializer.serialize({ 'available': True, })) return True elif self.nickname_not_set(): self.sock.sendall(self.serializer.serialize({ 'available': False, })) return False else: return True def get_nickname(self): return self.nickname or self.addr def nickname_not_set(self): return self.nickname == self.addr def nickname_available(self, nickname): return nickname not in [ client.nickname for client in self.server.client_threads ] def set_last_activity(self): self.last_activity = datetime.datetime.now() logging.info(msg=f' {self.get_nickname()}:' f' Set last activity to: {self.last_activity}') def send(self, content): """ Used to broadcast messages to all connected clients """ self.sock.sendall(self.serializer.serialize(content)) def run(self): self.__stop = False self.set_last_activity() while not self.__stop: try: ready, _, _ = select.select([self.sock], [], []) except OSError: return time.sleep(.5) if not ready: continue try: data = self.sock.recv(1024) except (OSError, ConnectionResetError): continue try: client = self.serializer.deserialize(data) except EOFError: continue self.set_last_activity() """ Server shouldn't allow multiple users with the same nickname """ if client['action'] == Action.NICKNAME: if not self.set_nickname(nickname=client['nickname']): return self.server.disconnect_client(client=self) """ If server gets "disconnect" from the client, notification to all other clients should also be sent. """ if client['action'] == Action.DISCONNECT: self.server.disconnect_client(client=self) self.server.send_notification( message=f'{self.get_nickname()} left the server.') return if client['action'] == Action.MESSAGE: logging.info( msg=f' {self.get_nickname()}: {client["message"]}') for thr in self.server.client_threads: thr.send(content=client)