|
| 1 | +import socket |
| 2 | +import threading |
| 3 | +import queue |
| 4 | +import time |
| 5 | + |
| 6 | +#to create a mutex (mutual exclusion) and ensure synchronized printing to avoid overlapping console output |
| 7 | +print_lock = threading.Lock() |
| 8 | + |
| 9 | +#queue so we can store messages from clients |
| 10 | +message_queue = queue.Queue() |
| 11 | + |
| 12 | +#stores the active connections with client IDs |
| 13 | +client_connections = {} |
| 14 | + |
| 15 | +#just func to handle communication with each connected client |
| 16 | +def handle_client(connection, address, client_id): |
| 17 | + #Output connection message when a client connects |
| 18 | + with print_lock: |
| 19 | + print(f"Connected to {client_id} at address: {address}") |
| 20 | + |
| 21 | + while True: |
| 22 | + #receiving data from the client |
| 23 | + data = connection.recv(1024).decode('utf-8') |
| 24 | + |
| 25 | + #But we check if data is empty maybe client disconnected |
| 26 | + if not data: |
| 27 | + break |
| 28 | + |
| 29 | + #output received message from the client |
| 30 | + with print_lock: |
| 31 | + print(f"Received from {client_id}: {data}") |
| 32 | + |
| 33 | + #the received message will be along with client_id into the message queue |
| 34 | + message_queue.put((client_id, data)) |
| 35 | + |
| 36 | + #if client disconnects close |
| 37 | + connection.close() |
| 38 | + |
| 39 | + #remove client IDs connection from the dictionary |
| 40 | + with print_lock: |
| 41 | + print(f"Connection with {client_id} closed.") |
| 42 | + del client_connections[client_id] |
| 43 | + |
| 44 | +#func to process messages |
| 45 | +def process_messages(): |
| 46 | + while True: |
| 47 | + #first we check if the message queue is not empty |
| 48 | + if not message_queue.empty(): |
| 49 | + with print_lock: |
| 50 | + #getting the client_id and message from the message queue |
| 51 | + client_id, message = message_queue.get() |
| 52 | + |
| 53 | + #entering a response for the client |
| 54 | + response = input(f"Enter your response for {client_id}: ") |
| 55 | + |
| 56 | + #another output response |
| 57 | + print(f"Response to {client_id}: {response}") |
| 58 | + |
| 59 | + #checking if the client is still connected |
| 60 | + if client_id in client_connections: |
| 61 | + #we send the response back to the client |
| 62 | + client_connections[client_id].send(response.encode('utf-8')) |
| 63 | + |
| 64 | + #1sec delay to handle safety |
| 65 | + time.sleep(1) |
| 66 | + |
| 67 | +#func to start the server |
| 68 | +def start_server(): |
| 69 | + #socket for server |
| 70 | + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 71 | + |
| 72 | + #Binding the socket to a specific address and port |
| 73 | + server_socket.bind(('localhost', 8888)) |
| 74 | + |
| 75 | + #listening for incoming connections (5 clients in the queue) |
| 76 | + server_socket.listen(5) |
| 77 | + |
| 78 | + #showing that the server is running |
| 79 | + with print_lock: |
| 80 | + print("Server is running and waiting for connections...") |
| 81 | + |
| 82 | + #used to assign unique client IDs to each connected client |
| 83 | + client_counter = 1 |
| 84 | + |
| 85 | + try: |
| 86 | + #"process_messages" function runs in a separate thread process messages from clients |
| 87 | + #The use of a daemon thread ensures that the thread terminates when the server program exits |
| 88 | + threading.Thread(target=process_messages, daemon=True).start() |
| 89 | + |
| 90 | + while True: |
| 91 | + #accepting new connection from a client |
| 92 | + connection, address = server_socket.accept() |
| 93 | + |
| 94 | + #generating a unique client ID |
| 95 | + client_id = f"Client {client_counter}" |
| 96 | + client_counter += 1 |
| 97 | + |
| 98 | + #storing the client connection in the dictionary |
| 99 | + client_connections[client_id] = connection |
| 100 | + |
| 101 | + #to handle communication with the client start a new thread |
| 102 | + client_thread = threading.Thread(target=handle_client, args=(connection, address, client_id)) |
| 103 | + client_thread.start() |
| 104 | + |
| 105 | + except KeyboardInterrupt: |
| 106 | + #keyboard interrupt |
| 107 | + with print_lock: |
| 108 | + print("Server shutting down.") |
| 109 | + finally: |
| 110 | + server_socket.close() |
| 111 | + |
| 112 | +if __name__ == "__main__": |
| 113 | + start_server() |
0 commit comments