0

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.

  1. Start the Server and listen for incoming connections

  2. start up other clients, let them connect to the server

  3. When the User Client connects, the server should detect it, and start the user thread

  4. User client should send data to the server telling it which client to connect to

  5. 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

1
  • I have a high level question here: at what moment do you detect the user client among all the clients? And will the server only have one single user at a time? I cannot really understand the goal of all that... Commented 12 hours ago

1 Answer 1

0

This is a bit long for a comment, hence the following.

Let's use your first posted code as the basis for modification. When your server gets a new connection, it starts a thread for worker function handle_client passing that connection. Now handle_client should expect the first message it receives to specify the endpoint that it wants its messages to be passed through to. Thus, handle_client now must open up a new client connection to that endpoint server. On success or error, a suitable response is sent back to the client.

Assuming success, handle_client now has two connections it is dealing with: (1) the original client connection that was passed to it and (2) the client connection it just created to the endpoint server. Let's call these connections, client1 and client2. What handle_client should now do is enter into a loop and poll these two connections to see if there is data to be received, retrieve that data, and finally sendall the message to the other connection. The issue then becomes how to do this pooling. Seemodule select and specifically the select.select function. You will have code something like:

...
BUFFER_SIZE = 1024

# client1 and client2 are the two connections that have
# to be "wired together".
while True:
    input_ready, _, _ = select.select([client1, client2], [], [])
    for recv_connection in input_ready:
        data = connection.recv(BUFFER_SIZE)
        send_connection = client1 if connection is client2 else client1
        send_connection.sendall(data)

You need to add code to test for closed connections and take appropriate action.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.