First commit

This commit is contained in:
AndrewTrieu
2023-04-02 15:52:45 +03:00
commit 932792a7de
4 changed files with 224 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.vscode/settings.json

68
README.md Normal file
View File

@@ -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 `@<nickname> <message>`.
- 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.

58
client.py Normal file
View File

@@ -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()

97
server.py Normal file
View File

@@ -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}")