10
\$\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
asked Aug 12, 2020 at 14:23
\$\endgroup\$

2 Answers 2

2
\$\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.
answered Nov 6, 2020 at 22:31
\$\endgroup\$
1
\$\begingroup\$

Optimization on the server side:

  1. In the function send_message - merge the 2 calls to socket.send to one call to socket.send. Also it will be better to replace the call for socket.send with socket.sendall as it will deal better with some problems.

  2. I don't see a reason to send the Header length in UTF-8, it can be a simple number so you will save the decoding time (which is negligible).

  3. In the function receive_message there is no need for while True as the function will always return after the first iteration.

  4. Replace threading with multi-processing as Python GIL will block your threads.

Your client has similar issues - so there is no point writing them down.

There are some other things that are not related to what you asked to be reviewed that I would improve - Type hinting, move your code to functions (mostly in the client), dealing with errors and communicating them (logging) and so on.

Overall it's good code.

Toby Speight
87.2k14 gold badges104 silver badges322 bronze badges
answered Feb 12, 2022 at 21:26
\$\endgroup\$
1
  • \$\begingroup\$ It's generally best to send the header length as characters - that allows better portability with other implementations (no need to worry about endianness etc). UTF-8 is a good choice for that (as is ASCII, given it's all digits - in which case the two are the same). \$\endgroup\$ Commented Feb 12, 2022 at 23:22

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.