Skip to content

Commit d8c02d5

Browse files
authored
Add files via upload
1 parent a7ed5e5 commit d8c02d5

File tree

24 files changed

+1954
-0
lines changed

24 files changed

+1954
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import socket
2+
import threading
3+
4+
message_lock = threading.Lock()
5+
6+
def start_client():
7+
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8+
client_socket.connect(('localhost', 8888))
9+
10+
while True:
11+
message = input("Enter a message (type 'exit' to end): ")
12+
13+
with message_lock:
14+
client_socket.send(message.encode('utf-8'))
15+
if message.lower() == 'exit':
16+
break
17+
18+
# Notify the client that the server is processing
19+
print("Waiting for server response...")
20+
21+
response = client_socket.recv(1024).decode('utf-8')
22+
print("Server response:", response)
23+
24+
client_socket.close()
25+
26+
if __name__ == "__main__":
27+
start_client()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import socket
2+
import threading
3+
4+
message_lock = threading.Lock()
5+
6+
def start_client():
7+
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
8+
client_socket.connect(('localhost', 8888))
9+
10+
while True:
11+
message = input("Enter a message (type 'exit' to end): ")
12+
13+
with message_lock:
14+
client_socket.send(message.encode('utf-8'))
15+
if message.lower() == 'exit':
16+
break
17+
18+
# Notify the client that the server is processing
19+
print("Waiting for server response...")
20+
21+
response = client_socket.recv(1024).decode('utf-8')
22+
print("Server response:", response)
23+
24+
client_socket.close()
25+
26+
if __name__ == "__main__":
27+
start_client()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
{print_lock} # ensures that only one thread at a time can access and print to the console.
2+
This helps avoid conflicts and improves the clarity of the printed messages.
3+
4+
5+
6+
{client_connections = {}} # stores the active connections with client IDs as keys and their corresponding socket connections as values
7+
Purp: The server needs to manage multiple client connections simultaneously when we use dictionary
8+
it can easily look up and interact with individual clients based on their unique identifiers (client IDs).
9+
Usage: When a new client connects its connection is added to this dictionary.
10+
This allows the server to send responses back to specific clients by referencing their client IDs.
11+
12+
13+
14+
{The client_counter} variable is used to assign unique client IDs to each connected client.
15+
Structure: client_counter is a numeric variable that starts at 1.
16+
It gets incremented by 1 for each new client, thereby providing a unique value for each client.
17+
Usage: When a new client connects the server generates a client ID by combining the string "Client " with the current value of client_counter.
18+
This results in client IDs like "Client 1", "Client 2", and so on.
19+
20+
21+
22+
threading.Thread(...): This creates a new thread using the Thread class from the threading module.
23+
target=process_messages: Specifies that the function process_messages will be the target function that the new thread runs.
24+
daemon=True:Daemon threads are background threads that are automatically terminated when the main program (in this case, the server) exits.
25+
.start(): initiates the execution of the thread causing it to begin running the process_messages function concurrently with the rest of the server.
26+
27+
28+
29+
30+
31+
Summary:
32+
33+
The script creates a server that handles multiple client connections concurrently.
34+
A mutex (print_lock) ensures synchronized printing to avoid overlapping console output.
35+
A message queue (message_queue) is used to store messages from clients.
36+
Client connections are tracked in a dictionary (client_connections) using unique client IDs.
37+
Separate threads are employed to handle communication with each connected client and to process messages concurrently.
38+
The server listens for incoming connections, assigns unique client IDs, and starts threads for communication handling.
39+
A daemon thread is used to process messages concurrently with the main server program.
40+
The script gracefully handles a keyboard interrupt, printing a shutdown message, and closes the server socket upon termination.
41+
42+
43+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import socket
2+
3+
def send_number_to_server(number):
4+
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
5+
client_socket.connect(('127.0.0.1', 12345))
6+
7+
while True:
8+
client_socket.send(str(number).encode('utf-8'))
9+
10+
data = client_socket.recv(1024)
11+
result = data.decode('utf-8')
12+
print(f"Server response: {result}")
13+
14+
user_input = input("Enter a number or type 'exit' to quit: ")
15+
if user_input.lower() == 'exit':
16+
break
17+
18+
try:
19+
number = int(user_input)
20+
except ValueError:
21+
print("Invalid input. Please enter a valid number.")
22+
23+
client_socket.close()
24+
25+
if __name__ == "__main__":
26+
user_input = input("Enter a number: ")
27+
try:
28+
number = int(user_input)
29+
send_number_to_server(number)
30+
except ValueError:
31+
print("Invalid input. Please enter a valid number.")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import socket
2+
import threading
3+
4+
def handle_client(client_socket, client_address):
5+
try:
6+
while True:
7+
# Receive data from the client in chunks of 1024 bytes
8+
data = client_socket.recv(1024)
9+
# If no more data is received, break out of the loop
10+
if not data:
11+
break
12+
13+
# Decode the received data to a UTF-8 string and print it on the server side
14+
command = data.decode('utf-8')
15+
print(f"Received from {client_address}: {command}")
16+
17+
# If the client sends 'exit', break out of the loop and close the connection
18+
if command.lower() == "exit":
19+
break
20+
21+
try:
22+
# If the received command is a valid integer, double it and send back to the client
23+
number = int(command)
24+
result = number * 2
25+
client_socket.send(str(result).encode('utf-8'))
26+
except ValueError:
27+
# If the conversion to integer fails, send an error message to the client
28+
client_socket.send("Invalid input. Please enter a valid number or type 'exit' to quit.".encode('utf-8'))
29+
30+
except Exception as e:
31+
# Catch any unexpected exceptions during communication and print for debugging
32+
print(f"Error handling client {client_address}: {str(e)}")
33+
finally:
34+
# Print a message about closing the connection and close the client socket
35+
print(f"Closing connection from {client_address}")
36+
client_socket.close()
37+
38+
def start_server():
39+
# Create a socket for the server with IPv4 address family and TCP socket type
40+
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
41+
# Bind the server socket to the address ('127.0.0.1', 12345)
42+
server_socket.bind(('127.0.0.1', 12345))
43+
# Allow up to 5 queued connections
44+
server_socket.listen(5)
45+
# Print a message indicating that the server is listening
46+
print("Server listening on port 12345")
47+
48+
try:
49+
while True:
50+
# Accept an incoming connection and get the client socket and address
51+
client_socket, client_address = server_socket.accept()
52+
# Print a message about the accepted connection
53+
print(f"Accepted connection from {client_address}")
54+
55+
# Create a new thread for handling communication with the connected client
56+
client_thread = threading.Thread(target=handle_client, args=(client_socket, client_address))
57+
# Start the thread to handle the client
58+
client_thread.start()
59+
60+
except KeyboardInterrupt:
61+
# Handle a keyboard interrupt (Ctrl+C) to gracefully shut down the server
62+
print("Server shutting down.")
63+
finally:
64+
# Close the server socket in the finally block to ensure cleanup
65+
server_socket.close()
66+
67+
# Main entry point
68+
if __name__ == "__main__":
69+
# Call the start_server function to initiate the server
70+
start_server()

0 commit comments

Comments
 (0)