diff --git a/chatserver/README.md b/chatserver/README.md index fd3050a..ac65127 100644 --- a/chatserver/README.md +++ b/chatserver/README.md @@ -1,11 +1,11 @@ # Running the chatserver -TBA +Use `python server.py` to run the server ## chatserver communication -Use the client to communicate with the chatserver +Use the client to communicate with the chatserver by running `python client.py` ## chatserver information -TBA +Included is the server and client written in Python. ## pcap information A demonstration of the chatserver has been captured with Wireshark in the diff --git a/chatserver/client.py b/chatserver/client.py new file mode 100644 index 0000000..e69de29 diff --git a/chatserver/server.py b/chatserver/server.py new file mode 100644 index 0000000..fd79b83 --- /dev/null +++ b/chatserver/server.py @@ -0,0 +1,139 @@ +import socket +import select + +CHATPORT = 8181 +BUFFER_SIZE = 1024 +MAX_CONNECT_QUEUE = 10 + +def main(): + # set the flag variable to true for infinite loop + quitFlag = True + # create a socket object + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + # set the socket options, SO_REUSEADDR allows to reuse the same port + # SO_KEEPALIVE enables the socket to send keepalive messages + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1) + # bind the socket to the address and port + server_socket.bind(('', CHATPORT)) + # listen for incoming connections + server_socket.listen(MAX_CONNECT_QUEUE) + # set the server socket to non-blocking + server_socket.setblocking(False) + + # create a list to store the sockets, server and client + socket_list = [server_socket] + # since a socket is associated with a username and a chatroom, + # and each one can only be associated one way at once, use dictionaries to + # associate with each other. + usernames = {} + chat_rooms = {} + # keep track of the states of each client connected, and if they are + # technically in an 'unsafe' state like in change username or chatroom mode. + client_states = {} + # let the user know the server is listening on port CHATPORT. + print(f"Chatserver listening on port: {CHATPORT}") + + # infinite loop for the program + while quitFlag: + # use the select function to monitor the sockets + read_sockets, _, _ = select.select(socket_list, [], []) + # loop through the sockets + for sock in read_sockets: + # if the socket is the server socket, accept the connection + if sock == server_socket: + client_socket, client_address = server_socket.accept() + client_socket.setblocking(False) + socket_list.append(client_socket) + print("\nAccepted connection from new client.") + client_socket.send(b"\nWelcome to the chat server!\n") + client_socket.send(b"\nUse '%!' to exit, or '%?' for help.\n") + client_socket.send(b"\nType in a username: \n") + client_states[client_socket] = "WAITING_FOR_USERNAME" + # otherwise the socket is a client socket. + else: + # try to receive the message from the client + try: + message = sock.recv(BUFFER_SIZE).decode('utf-8') + if message: + state = client_states.get(sock, "NORMAL") + if state == "WAITING_FOR_USERNAME": + usernames[sock] = message.strip() + chat_rooms[sock] = "default" + sock.send(b"\nUsername accepted. You are now in the 'default' chatroom.\n") + client_states[sock] = "NORMAL" + elif state == "WAITING_FOR_NEW_USERNAME": + usernames[sock] = message.strip() + sock.send(b"\nUsername changed successfully.\n") + print(f"\nClient switched username to {usernames[sock]}.") + client_states[sock] = "NORMAL" + elif state == "WAITING_FOR_NEW_CHATROOM": + old_chatroom = chat_rooms[sock] + chat_rooms[sock] = message.strip() + sock.send(f"\nSwitched to chatroom '{chat_rooms[sock]}.'\n".encode('utf-8')) + print(f"\nClient switched to chatroom '{chat_rooms[sock]}.'") + if old_chatroom != "default" and old_chatroom not in chat_rooms.values(): + print(f"\nChatroom '{old_chatroom}' is empty and removed.") + client_states[sock] = "NORMAL" + else: + if message.strip() == "%!": + sock.close() + socket_list.remove(sock) + del usernames[sock] + del chat_rooms[sock] + del client_states[sock] + print("\nClient has closed connection.") + elif message.strip() == "%?": + print("\nClient requested help.") + sock.send(b"Help: (%?)\n") + sock.send(b"Send messages like normal. Use enter to send a message.\n") + sock.send(b"Available Commands:\n") + sock.send(b"\t%? : Show this help prompt.\n") + sock.send(b"\t%! : Exit from the Chat Server.\n") + sock.send(b"\t%* : Switch to another chatroom.\n") + sock.send(b"\t%$ : Change Username.\n") + elif message.strip() == "%*": + sock.send(b"\nEnter chatroom name: \n") + client_states[sock] = "WAITING_FOR_NEW_CHATROOM" + elif message.strip() == "%$": + sock.send(b"\nEnter new username: \n") + client_states[sock] = "WAITING_FOR_NEW_USERNAME" + else: + username = usernames[sock] + chat_room = chat_rooms[sock] + send_message = f"{username} >> {message}".encode('utf-8') + for client_sock in socket_list: + if client_sock != server_socket and chat_rooms.get(client_sock) == chat_room: + client_sock.send(send_message) + else: + sock.close() + socket_list.remove(sock) + if sock in usernames: + del usernames[sock] + if sock in chat_rooms: + old_chatroom = chat_rooms[sock] + del chat_rooms[sock] + if old_chatroom != "default" and old_chatroom not in chat_rooms.values(): + print(f"\nChatroom '{old_chatroom}' is empty and has been removed.") + if sock in client_states: + del client_states[sock] + print("\nClient has closed connection.") + except Exception as err: + print(f"Error: {err}") + sock.close() + socket_list.remove(sock) + if sock in usernames: + del usernames[sock] + if sock in chat_rooms: + old_chatroom = chat_rooms[sock] + del chat_rooms[sock] + if old_chatroom != "default" and old_chatroom not in chat_rooms.values(): + print(f"\nChatroom '{old_chatroom}' is empty and has been removed.") + if sock in client_states: + del client_states[sock] + print("\nClient has closed connection.") + + server_socket.close() + +if __name__ == "__main__": + main() \ No newline at end of file