def handle_send(input_box: tk.Text): """ Executed when the user presses send. It will get and clear the text box then send the message. The message will be put straight into the list of messages on the client side. This is done so that the message doesn't need to be sent to the server and back again. If the server cannot be contacted, a message is displayed to the user to restart. Parameter: input_box (tk.Text): Text box containing the message """ msg = input_box.get("1.0", tk.END).rstrip() input_box.delete("1.0", tk.END) try: mp.send_msg_protocol(s, msg, NAME) # Create a label with the clients message and add it to the frame label = tk.Label(text='You:\n' + msg, master=frame, justify='left', wraplength=LABEL_WIDTH, relief='raised') label.pack(anchor='w') if msg == DISCONNECT: window.destroy() s.close() except ConnectionAbortedError: msgRcvQueue.put('Lost connection with server, please restart') except OSError: msgRcvQueue.put('Lost connection with server, please restart')
def sendRoomDetails(conn: socket): """ Sends the list of clients and rooms to the given connection. Paramters: conn (socket): Connection to send list to """ data = tabulate([list(e.values())[1:] for e in client_list.getList()], headers=client_list.getList()[0].keys()) mp.send_msg_protocol(conn, data, NAME)
def accept_connection(sock: socket): """ Accepts a connection given a socket object. If the initial connection times out, (None, None) is returned. If the connection to get the clients name times out after MAX_RETIRES, then the connection is aborted. Parameters: sock (socket): The socket to connect to Returns: (socket, tuple): A tuple containing the socket object and address of the connecting process. Or None, None if the connection times out. """ try: conn, addr = sock.accept() except socket.timeout: return (None, None) retries = 0 while True: try: client_name = mp.recv_msg_protocol(conn) client_list.addToList(conn, client_name, DEFAULT_ROOM) send_help(conn) return (conn, addr) except socket.timeout: if retries < MAX_RETIRES: retries += 1 print('Retrying...') else: # If the user takes too long to respond with a name # close the connection print(f"[CLOSING] {addr} took too long to respond") conn.close() return (None, None)
def sendMsg(conn: socket, msg: str): """ Given a connection and message, this will send the message to all clients in the chat room (except itself). Parameters: conn (socket): The client that is sending the message msg (str): The message to send """ current_chat_room = client_list.getConnRoom(conn) send_list = client_list.connectionsInRoom(current_chat_room) send_list.remove(conn) if send_list: for c in send_list: mp.send_msg_protocol(c, f'{client_list.getName(conn)}:\n {msg}', NAME)
def on_close(): """ Funciton to handle when the user closes the window instead of typing the DISCONNECT message. First notifies the server that you are disconnecting, then closes the socket and window. If the socket was already closed (say by the server), then simply destroy the window. """ if messagebox.askokcancel("Quit", "Do you want to quit?"): try: mp.send_msg_protocol(s, DISCONNECT, NAME) s.close() except ConnectionAbortedError: pass except OSError: pass finally: window.destroy()
def send_help(conn: socket): """ Sends the help message to the inputted connection. This message tells the user how to use the chat room Parmeters: conn (socket): The connection to send the message to """ help_message = ('Welcome to ChatRooms. The following commands' ' are currently supported:\n') help_message += HELP_MESSAGE + '\n' help_message += DISSCONNECT_MESSAGE + '\n' help_message += MOVE_ROOM_MESSAGE + '\n' help_message += LEAVE_ROOM_MESSAGE + '\n' help_message += ROOM_DETAILS_MESSGAE + '\n' mp.send_msg_protocol(conn, help_message, NAME)
def main(): connected = False dialog = ClientSetUp() window.protocol("WM_DELETE_WINDOW", dialog.on_close) window.mainloop() NAME, ip, port = (dialog.name, dialog.ip, dialog.port) while not connected: try: port = int(port) addr = (ip, port) s.connect(addr) connected = True except ConnectionRefusedError: dialog.retry(name=NAME) NAME, ip, port = (dialog.name, dialog.ip, dialog.port) except ValueError: dialog.retry(name=NAME, ip=ip) NAME, ip, port = (dialog.name, dialog.ip, dialog.port) except Exception: # Assume all other exceptions are because of failed connection dialog.retry(name=NAME) NAME, ip, port = (dialog.name, dialog.ip, dialog.port) dialog.destroy() thread_recv = threading.Thread(target=handle_recv) thread_recv.start() setUpWindow() if not NAME: # If name was not entered, create a random name NAME = "User" + str(random.randint(0, 1000)) # Send the first message that initialises the connection. # This simply involves setting the name of this client on the server. try: mp.send_msg_protocol(s, NAME, NAME) except Exception: # If this fails, the initialisation failed so client is not # connected to server properly msgRcvQueue.put('Connection not set up properlly, restart application') window.mainloop()
def updateRoom(conn: socket, chat_room: str): """ Leaves the connections current chat romm and changes to another. Ensures that the chat room is valid before changing for the given connection. Parameters: conn (socket): The client to update chat_room (str): The room to join prepended with {CREATE_ROOM} """ new_room = chat_room[len(MOVE_ROOM):].strip() if new_room == client_list.getConnRoom(conn): # If you're moving into the same room, do nothing pass elif not new_room == '': leaveRoom(conn) client_list.updateChatRoom(conn, new_room) sendMsg(conn, f'{client_list.getName(conn)} has entered the chat') else: msg = f'"{new_room}" is not a valid name.' mp.send_msg_protocol(conn, msg, NAME)
def handle_client(conn: socket, addr: tuple): """ Handles the client connection in a separate thread to the one that accepts the connection. This thread will be responsible for processing what the client sends. Parameter: conn (socket): The socket that the connection is using addr (tuple) : The host and port tuple Returns: None """ print(f'[NEW CONNECTION] {addr} has connected') sendMsg(conn, f'{client_list.getName(conn)} has entered the chat') connected = True while connected: try: msg = mp.recv_msg_protocol(conn) # If msg is not a special message then send to all. # Otherwise handle request based on input if not msg: # Do nothing if header was invalid pass if msg == HELP: send_help(conn) elif msg == DISCONNECT: connected = False disconnect(conn) elif re.match(MOVE_ROOM + "*", msg): updateRoom(conn, msg) elif msg == LEAVE_ROOM: leaveRoom(conn) elif msg == ROOM_DETAILS: sendRoomDetails(conn) else: sendMsg(conn, msg) except socket.timeout: # Ignore timeouts pass except ConnectionResetError: # This error can be thrown when the client disconnects connected = False disconnect(conn) except OSError: # Can happen if server shutsdown connction connected = False disconnect(conn)
def handle_recv(): """ This function runs in it's own thread. It runs a blocking call to recieve data from the server (messages from other users). This call fails when the socket is closed, which happens in the other thread. """ while True: try: data = mp.recv_msg_protocol(s) # Ignore if data is empty or the header was invalid if data: msgRcvQueue.put(data) except ConnectionAbortedError: break except OSError: break