8
\$\begingroup\$

I made a chatroom with a server and client python files, using Sockets and Threading as a project. I'm interested to acquire advice, and optimizations i could make to my program. Also please give me tips on sockets and threading in general, because I'm fairly new to the topic, and i would want to learn whatever is relevant in sockets in general. Thank you.

Server.py:

import threading

# constants
DISCONNECT_COMMAND = '/disconnect'
HEADER_LENGTH = 10
FORMAT = 'utf-8'
IP = '127.0.1.1'
PORT = 1234
SERVER_ADDRESS = (IP, PORT)
print("[SERVER] Starting Server...")

# creating and listening to socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(SERVER_ADDRESS)
server_socket.listen()

# clients information
clients = {} # {client_socket: {'username': username, 'address': address}}
messages = [] #


def close_connection(client_socket):
    print(f"[DISCONNECTION] {clients[client_socket]['username']} has disconnected!")
    del clients[client_socket]
    client_socket.close()


def send_message(socket, message):
    try:
        socket.send(bytes(f"{len(message):<{HEADER_LENGTH}}", FORMAT))
        socket.send(bytes(message, FORMAT))
    except:
        print(f"[ERROR] SENDING '{message}' to {clients[socket]['username']}")


def receive_message(client_socket):
    while True:
        message_length = client_socket.recv(HEADER_LENGTH).decode(FORMAT)
        message = client_socket.recv(int(message_length)).decode(FORMAT)
        return message


def send_to_all_clients(message, sender_socket):
    user_message = f"{clients[sender_socket]['username']} > {message}"
    messages.append(user_message)
    print(user_message)
    for client_socket in clients:
        if sender_socket != client_socket:
            send_message(client_socket, user_message)


def handle_client(client_socket, client_address):
    # user name and server prompt for new connections
    username = receive_message(client_socket)
    clients[client_socket] = {'username': username, 'address': client_address}
    print(f"[ACTIVE CONNECTIONS] {threading.active_count() - 1}")
    print(f"[NEW CONNECTION] {username} has connected!")

    # server prompt for connection, and receiving previous messages of server.
    send_message(client_socket, f'You have connected to the server with the username "{clients[client_socket]["username"]}"!')
    send_message(client_socket, str(len(messages))) # sending of previous messages to new client
    for i in range(len(messages)):
        send_message(client_socket, messages[i])

    # receiving messages and sending it to other clients
    connected = True
    while connected:
        try:
            message = receive_message(client_socket)
            if message == DISCONNECT_COMMAND:
                connected = False

            send_to_all_clients(message, client_socket)

        except:
            connected = False

    close_connection(client_socket)

# listening for clients
print("[LISTENING] Listening for Clients")
while True:
    client_socket, client_address = server_socket.accept()
    handling_thread = threading.Thread(target=handle_client, args=[client_socket, client_address])
    handling_thread.start()

Client.py:

import socket
import threading

# Server Information
SERVER_ADDRESS = ('127.0.1.1', 1234)
HEADER_LENGTH = 10
FORMAT = 'utf-8'
DISCONNECT_COMMAND = '/disconnect'

def send_message(message):
    message_length = f"{len(message):<{HEADER_LENGTH}}"
    client_socket.send(bytes(message_length, FORMAT))
    client_socket.send(bytes(message, FORMAT))


def receive_message():
    while True:
        message_length = client_socket.recv(HEADER_LENGTH).decode(FORMAT)
        if message_length:
            message = client_socket.recv(int(message_length)).decode(FORMAT)
            return message


def receive_messages_in_real_time():
    while True:
        message = receive_message()
        print(message)


# Connecting to Server
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect(SERVER_ADDRESS)

# Username Prompt
username = input("Enter your username:\n")
send_message(username)

# Server Message for successful connection, and previous messages in server
print(receive_message())
number_of_messages = receive_message()
for _ in range(int(number_of_messages)):
    print(receive_message())

# thread for receiving messages
receiving_thread = threading.Thread(target=receive_messages_in_real_time)
receiving_thread.daemon = True
receiving_thread.start()

# messaging in the server
connected = True
while connected:
    message = input()
    send_message(message)
    if message == DISCONNECT_COMMAND:
        connected = False

\$\endgroup\$
1
\$\begingroup\$

Looks fine, I have no tips. You did good. In a real application, you might want to tuck things into classes or code more defensively. Make sure you deal with frequent client disconnects (they don't properly close the connection) in a way where the server doesn't eventually die.

If you're looking for challenges, you could:

  • Write a class which lets someone write a client bot, then write a small bot using it. I suggest event handlers or callbacks.
  • Do the same thing again, UDP
  • Learn how to test your TCP scaling, up to at least 100-1K clients. Then improve TCP scaling to that range.
  • When it gets big enough, your limit will be traffic. Add individual chat rooms. in groups of 10 people per chat room. Now in theory, it should be no problem to handle 1 message per second for the whole network. Actually support 10K clients.
| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy