from twisted.internet.protocol import DatagramProtocol from twisted.internet import reactor from time import sleep import sys def address_to_string(address): ip, port = address return ':'.join([ip, str(port)]) class ServerProtocol(DatagramProtocol): def __init__(self): self.active_sessions: dict[Session] = {} self.registered_clients: dict[Client] = {} # Checks whether a client with the given client_id is already registered. def is_client_registered(self, client_id): return client_id in self.registered_clients # Checks if a session has already been registered with the given session_id. def session_exists(self, session_id) -> bool: return session_id in self.active_sessions # Creates a new session for players to join. def create_session(self, session_id, host_client_id) -> int: if self.session_exists(session_id): # Tried to create existing session return 1 self.active_sessions[session_id] = Session(session_id, host_client_id, self) return 0 # Closes an existing session. Must disconnect all clients manually before calling this! def close_session(self, s_id): try: del self.active_sessions[s_id] except KeyError: print("Tried to terminate non-existing session") # Registers a client and connects them to a given session. def register_client(self, session_id, client_id, client_name, client_ip, client_port) -> int: if self.is_client_registered(client_id): print("Client %s is already registered." % [client_id]) return 1 if not self.session_exists(session_id): print("Client registered for non-existing session") return 2 if len(self.active_sessions[session_id].registered_clients) >= 2: print("Trying to join full session") return 3 new_client = Client(session_id, client_id, client_name, client_ip, client_port) self.registered_clients[client_id] = new_client self.active_sessions[session_id].register_client(new_client) return 0 # Disconnects a client and removes them from the registered_clients. def disconnect_client(self, session_id, client_id): try: del self.registered_clients[client_id] self.active_sessions[session_id].remove_client(client_id) except KeyError: print("Tried to disconnect unregistered client %s." % [client_id]) # Checks whether the given client owns any session. Returns the session ID if true. # Returns an empty string otherwise. def owns_session(self, client_id) -> str: for session_id in self.active_sessions: if self.active_sessions[session_id].host_client_id == client_id: return session_id return "" # Sends a message to a client specified by their address. def send_message(self, msg, address): self.transport.write(bytes(msg, "utf-8"), address) # Executed when data is received from a client. def datagramReceived(self, datagram, address): """Handle incoming datagram messages.""" print(datagram) data_string = datagram.decode("utf-8") msg_type = data_string[:2] print(address) client_ip, client_port = address split_data = data_string.split(":") if msg_type == "cr": # create lobby error = self.create_session(split_data[1], split_data[2]) if error == 1: # tried to create existing session print("Could not create session; already exists") self.transport.write(bytes('c1', "utf-8"), address) return # TODO no error handling for register_client here yet print(self.register_client(split_data[1], split_data[2], split_data[3], client_ip, client_port)) print(client_port) print("CLIENT PORT") print("successfully created session and registered client") self.transport.write(bytes('c0', "utf-8"), address) elif msg_type == "jn": # join lobby error = self.register_client(split_data[1], split_data[2], split_data[3], client_ip, client_port) print(client_port) print("CLIENT PORT") if error == 1: # client already registered self.send_message('j1', address) return elif error == 2: # session does not exist self.send_message('j2', address) return elif error == 3: # session full self.send_message('j3', address) return self.send_message('j0', address) print("joined successfully") elif msg_type == "lv": session_id = split_data[1] leaving_client_id = split_data[2] owned_session = self.owns_session(leaving_client_id) if owned_session == "": # client is only connected to a session self.disconnect_client(session_id, leaving_client_id) print("disconnected client from session") else: # client owns a session # disconnect all clients clients_to_delete = self.active_sessions[session_id].registered_clients.copy() for client in clients_to_delete: print(clients_to_delete) print(client) client_address = (client.client_ip, client.client_port) print(client_address) self.send_message('ex', client_address) self.disconnect_client(client.session_id, client.client_id) self.close_session(session_id) print("host gone; disconnected clients and closed session") class Session: def __init__(self, session_id, host_client_id, server): self.id = session_id self.host_client_id = host_client_id self.server = server self.registered_clients: list[Client] = [] # Registers a client in the session. def register_client(self, client): if client in self.registered_clients: # Client is already registered return self.registered_clients.append(client) def remove_client(self, client_id): for client in self.registered_clients: if client.client_id == client_id: self.registered_clients.remove(client) class Client: def __init__(self, session_id, client_id, client_name, client_ip, client_port): self.session_id = session_id self.client_id = client_id self.client_name = client_name self.client_ip = client_ip self.client_port = client_port if __name__ == '__main__': if len(sys.argv) < 2: print("Usage: ./server.py PORT") sys.exit(1) port = int(sys.argv[1]) reactor.listenUDP(port, ServerProtocol()) print('Listening on *:%d' % (port)) reactor.run()