From 932792a7dee43964b695e638d87b248c186a6c40 Mon Sep 17 00:00:00 2001 From: AndrewTrieu Date: Sun, 2 Apr 2023 15:52:45 +0300 Subject: [PATCH] First commit --- .gitignore | 1 + README.md | 68 ++++++++++++++++++++++++++++++++++++++ client.py | 58 ++++++++++++++++++++++++++++++++ server.py | 97 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 224 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 client.py create mode 100644 server.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c6f9a44 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode/settings.json diff --git a/README.md b/README.md new file mode 100644 index 0000000..8b9f3de --- /dev/null +++ b/README.md @@ -0,0 +1,68 @@ +# Multi-user chat server + +The system is a Python-based distributed chat application consisting of two main components: a client and a server. The client and server will communicate with each other over the internet using the Transmission Control Protocol (TCP). + +The server will be responsible for managing the chat room and routing messages between clients. When a client connects to the server, it will be asked to provide a nickname, which will be used to identify the client in the chat room. The server will keep track of all connected clients and their nicknames in a dictionary. + +The server will have two main functions: handling client connections and broadcasting messages to all connected clients. When a client connects to the server, a new thread will be created to handle that client's connection. The thread will listen for incoming messages from the client and route them to the appropriate recipients (either all connected clients or a specific client in the case of private messages). + +The client will be responsible for displaying the chat room interface to the user and sending messages to the server. When a user opens the chat room, the client will connect to the server and provide a nickname. The client will then be able to send messages to the server, which will be broadcast to all connected clients or sent as a private message to a specific client. + +- Two types of transparency have been implemented in the chat system: location transparency and access transparency. Location transparency means clients don't need to know the physical location of other clients to communicate with them, achieved by the server routing messages between clients. Access transparency means clients only need to know the interface provided by the server, which is connecting to the server and sending messages. +- The system has used a server-client architecture to achieve scalability, allowing the server to handle multiple client connections simultaneously. To handle the load of many clients sending messages at once, threading has been used in the implementation. +- The chat system handles failures gracefully by removing clients from the list of active clients and notifying remaining clients if a client disconnects unexpectedly. If the server fails, the system will stop working, but redundancy could be added by implementing multiple servers. + +## Diagram + +```mermaid +sequenceDiagram + participant Client + participant Server + participant OtherClient + + Client->>Server: Connects to server + Server-->>Client: Asks for nickname + Client->>Server: Sends nickname + Server->>OtherClient: Notifies other clients that Client has joined + loop Chatting + alt Public message + Client->>Server: Sends a message + Server->>OtherClient: Sends the message to other clients + OtherClient-->>Server: Acknowledges receipt of message + Server-->>Client: Sends a copy of the message to Client + else Private message + Client->>Server: @OtherClient message + Server->>OtherClient: Notifies OtherClient of the private message + OtherClient-->>Server: Acknowledges receipt of message + Server-->>Client: Sends a copy of the private message to Client + end + end + Client->>Server: Requests to disconnect + Server->>OtherClient: Notifies other clients that Client has left + Server-->>Client: Disconnects from server +``` + +## Functionality + +- Client connects to the server and provides a nickname. +- Server receives the nickname from the client and adds the client to the list of connected clients. +- Client sends a message to the server. +- Server receives the message from the client and routes it to the appropriate recipients (either all connected clients or a specific client in the case of private messages). +- Server broadcasts the message to all connected clients. +- Client receives the message from the server and displays it in the chat room interface. +- Client sends a private message to a specific client by typing `@ `. +- Server routes the private message to the intended recipient. +- Intended recipient receives the private message from the server and displays it in their chat room interface. +- Client disconnects from the server using the `/q` command. + +## Starting the chat room + +Start the server by running the `server.py` script: + + $ python server.py + +After the server has started, start the client by running the `client.py` script: + + $ python client.py + +Multiple clients can be started by running the `client.py` script in multiple terminal windows. diff --git a/client.py b/client.py new file mode 100644 index 0000000..7050825 --- /dev/null +++ b/client.py @@ -0,0 +1,58 @@ +import socket +import threading + +HOST = '127.0.0.1' # IP address for the server +PORT = 3000 # Port number for the server + +# Function to receive messages from the server + + +def receive_messages(client_socket): + while True: + try: + message = client_socket.recv(1024).decode() + print(message) + except: + # If an error occurs, close the client socket and exit the thread + client_socket.close() + break + + +# Create a socket object +client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +# Connect to the server +client_socket.connect((HOST, PORT)) + +# Create a new thread to receive messages from the server +receive_thread = threading.Thread( + target=receive_messages, args=(client_socket,)) +receive_thread.start() + +# Send a welcome message to the client +print("Welcome to the chat room!") + +# Send nickname to the server +nickname = input("Please enter your nickname: ") +client_socket.sendall(nickname.encode()) + +while True: + message = input() + + if message == "\q": + # Send a message to the server indicating that the user wants to quit + client_socket.sendall("\q".encode()) + # Exit the program immediately + break + elif message.startswith("@"): + # If user types a message starting with "@" it is considered a private message + recipient = message.split(" ")[0][1:] + private_message = message.split(" ", 1)[1] + client_socket.sendall(f"@{recipient} {private_message}".encode()) + + else: + # Otherwise, send the message to the server to broadcast to all connected clients + client_socket.sendall(message.encode()) + +# Close the client socket +client_socket.close() diff --git a/server.py b/server.py new file mode 100644 index 0000000..49d30ac --- /dev/null +++ b/server.py @@ -0,0 +1,97 @@ +import socket +import threading + +HOST = '127.0.0.1' # IP address for the server +PORT = 3000 # Port number for the server +MAX_CONNECTIONS = 10 # Maximum number of clients allowed to connect + +# Dictionary to store connected clients and their nicknames +clients = {} + +# Function to handle each client connection + + +def handle_client(conn, addr): + print(f"[NEW CONNECTION] {addr} connected.") + + # Ask the client to set a nickname + nickname = conn.recv(1024).decode() + + # Add the client to the dictionary of connected clients + clients[conn] = nickname + + # Notify all other clients that a new client has joined the chat + broadcast(f"{nickname} has joined the chat!".encode()) + + while True: + # Receive message from the client + message = conn.recv(1024).decode() + if message: + # Check if the message is a private message to a specific client + if message == "\q": + # If the message is "quit", remove the client from the dictionary of connected clients + del clients[conn] + # Notify all other clients that the client has left the chat + broadcast(f"{nickname} has left the chat.".encode()) + print(f"[DISCONNECTED] {addr} disconnected.") + # Close the client connection + conn.close() + break + elif message.startswith("@"): + recipient = message.split(" ")[0][1:] + message = message.split(" ", 1)[1] + send_private_message(conn, recipient, message) + else: + # Broadcast the message to all connected clients + broadcast(f"{nickname}: {message}".encode()) + + else: + # If message is empty, remove the client from the dictionary of connected clients + del clients[conn] + # Notify all other clients that the client has left the chat + broadcast(f"{nickname} has left the chat.".encode()) + # Close the client connection + conn.close() + break + +# Function to broadcast a message to all connected clients + + +def broadcast(message): + for client in clients: + client.sendall(message) + +# Function to send a private message to a specific client + + +def send_private_message(sender_conn, recipient, message): + for client, nickname in clients.items(): + if nickname == recipient: + client.sendall( + f"(Private) {clients[sender_conn]}: {message}".encode()) + sender_conn.sendall( + f"(Private) {clients[sender_conn]}: {message}".encode()) + break + + +# Create a socket object +server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + +# Bind the socket object to the specified host and port number +server_socket.bind((HOST, PORT)) + +# Listen for incoming connections +server_socket.listen(MAX_CONNECTIONS) + +print(f"[LISTENING] Server is listening on {HOST}:{PORT}.") + +while True: + # Accept incoming connections + conn, addr = server_socket.accept() + + # Create a new thread to handle the client connection + thread = threading.Thread(target=handle_client, args=(conn, addr)) + thread.start() + + # Print the number of active connections + print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")