The project I'm working on is a simulation/ mock up of a VPN service. I've figured out how to connect multiple clients to a single server using loops and multi-threading, but now I'm struggling to figure out selectively pass information between those threads maintain each connection. The process as I've envisioned it is this.
Start the Server and listen for incoming connections
start up other clients, let them connect to the server
When the User Client connects, the server should detect it, and start the user thread
User client should send data to the server telling it which client to connect to
Server should then start tunneling info between the User client and the selected Client
The problem is, because of the way the loop to connect all the clients works I don't have a way of selecting which connections remain open, or which ones to tunnel information through. All the connections are created at once under the same name, and call the same function to perform their processes. Here is the code I'm working from as a base.
def handle_client(conn, addr):
print(f"Connection established with {addr}.")
while True:
try:
data = conn.recv(1024) # Receive data from the client
if not data:
break # Terminate the connection if no data is received
print(f"Received from {addr}: {data.decode()}")
conn.sendall(f"Server response: {data.decode()}".encode()) # Send the received data back to the client
except ConnectionResetError:
print(f"Client {addr} has disconnected.")
break
conn.close()
print(f"Connection with {addr} closed.")# Start the server
def start_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f"Server listening on {HOST}:{PORT}...")
while True:
conn, addr = server_socket.accept() # Accept a client connection
thread = threading.Thread(target=handle_client, args=(conn, addr))
thread.start() # Start a new thread for each client
print(f"Active connections: {threading.activeCount() - 1}")
if __name__ == "__main__":
start_server()
And here is the modified code that I wrote. My thought was to do away with the while-True loop, and only have a select, limited number of other client connections to be maintained, that could be called upon by name for ease of access. I also added a dedicated function to service the User-Client connection, to be called when the appropriate ip-address was detected. Finally, I added a queue, to tunnel information between the user and the selected Client.
##Code to handle user cLient
def handle_user(conn, addr,in_q):
print(f"Connection established with {addr}.")
while True:
try:
data = conn.recv(1024).decode() # Receive data from the client
if not data:
break # Terminate the connection if no data is received
in_q.put(data)
time.sleep(3)
toClient = in_q.get()
conn.sendall({data.encode()})
except ConnectionResetError:
print(f"Client {addr} has disconnected.")
break
conn.close()
print(f"Connection with {addr} closed.")# Start the server
#code to handle other clients
def handle_client(conn,addr):
print(f"Connection established with {addr}.")
while True:
try:
conn.sendall()
data = conn.recv(1024) # Receive data from the client
if not data:
break # Terminate the connection if no data is received
print(f"Received from {addr}: {data.decode()}")
conn.sendall(f"Server response: {data.decode()}".encode()) # Send the received data back to the client
except ConnectionResetError:
print(f"Client {addr} has disconnected.")
break
conn.close()
def start_server():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server_socket:
server_socket.bind((HOST, PORT))
server_socket.listen()
print(f"Server listening on {HOST}:{PORT}...")
q = Queue() #create a new queue
while True:
conn, addr = server_socket.accept() # Accept a client connection
if addr == '10.9.0.2':
User_thread = threading.Thread(target=handle_user, args=(conn, addr,q))
User_thread.start() # Start thread to handle User
conn.sendall(f"Which server would you like to connect to?\n0)to termiante connection\n1)UK\n2)Nowray\n3)Egypt")
data = conn.recv(1024).decode() # Receive data from the client
if data == 1:
t1.start()
elif data == 2:
t2.start()
elif data == 3:
t3.start()
elif addr == '10.9.0.3':
t1 = threading.Thread(target=handle_client, args=(conn, addr,q))
# Start a new thread for each client
elif addr == '10.9.0.4':
t2 = threading.Thread(target=handle_client, args=(conn, addr,q))
t2.start() # Start a new thread for each client
elif addr == '10.9.0.5':
t3 = threading.Thread(target=handle_client, args=(conn, addr,q))
t3.start() # Start a new thread for each client
print(f"Active connections: {threading.activeCount() - 1}")
if __name__ == "__main__":
start_server()
As you can see, Its a mess. My first thought was to have the handle-user thread talk to the user, and get their selection of which other client they'd like to connect to. But that fell through when I realized there was no clean way to return that information to Main without ending that thread and closing the connection with the User. So I tried moving that process to the Main function itself, and starting the other threads based on the user's connection, but now I'm not sure if the old conn passed to the client threads will be overwritten by the loop if the threads are only started on the iteration where the user connects.
TLDR: I need a way to choose which threads I'm working with at any given time, based on input from the User thread