How to build a multi-client TCP server in Python

Based on contributions by Luthfiandra.

Use this when several clients — for example a laptop and a robot controller — need to talk to the same Python server over the network at the same time, and messages from one client should be forwarded to the others.

What you need

  • Python 3 on the machine that will act as the server (a lab PC works well).
  • All clients on the same network/IP range as the server.
  • The server’s IP address and a free port (e.g. 20002).

Fill in your own server IP address and port below. 192.168.123.100 is only an example — check the server’s actual address (e.g. with ipconfig on Windows or ip addr on Linux).

Steps

1. Write the server

The server listens for incoming connections, starts a new thread per client so it can serve everyone at once, and forwards any message it receives to all other connected clients.

import socket
import threading

# Server address and port - replace with your own
SERVER_ADDRESS = ("192.168.123.100", 20002)

# Create a TCP/IP socket
server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_sock.bind(SERVER_ADDRESS)
server_sock.listen(5)  # queue up to 5 pending connections

print(f"Server is listening on {SERVER_ADDRESS}")

clients = []  # keeps track of connected clients


def handle_client(connection, client_address):
    """Handle one client connection in its own thread."""
    print(f"Connection from {client_address}")
    clients.append(connection)
    try:
        while True:
            data = connection.recv(1024)
            if not data:
                print(f"No data from {client_address}. Closing connection.")
                break

            print(f"Received from {client_address}: {data.decode()}")
            forward_message(data, connection)
    except Exception as e:
        print(f"Error with {client_address}: {e}")
    finally:
        clients.remove(connection)
        connection.close()
        print(f"Connection with {client_address} closed.")


def forward_message(message, sender_connection):
    """Forward a message to every connected client except the sender."""
    for client in clients:
        if client != sender_connection:
            try:
                client.sendall(message)
            except Exception as e:
                print(f"Failed to send message to {client}: {e}")


while True:
    connection, client_address = server_sock.accept()
    client_thread = threading.Thread(target=handle_client, args=(connection, client_address))
    client_thread.start()

This creates a server that can handle up to 5 pending connections and relays any message it receives to all other clients. If you only want to talk to one specific client, filter on client_address instead of broadcasting to the whole list.

2. Write a client (PC or laptop)

Make sure the client’s IP address is in the same range as the server’s (in this example, 192.168.123.xxx). Check your own IP with ipconfig (Windows) or ip addr/ifconfig (Linux/macOS).

import socket
import threading

SERVER_ADDRESS = ("192.168.123.100", 20002)  # must match the server


def receive_messages(sock):
    """Continuously print any message received from the server."""
    while True:
        try:
            message = sock.recv(1024)
            if not message:
                print("Disconnected from the server.")
                break
            print(f"Received: {message.decode()}")
        except Exception as e:
            print(f"Error receiving message: {e}")
            break


def main():
    client_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    try:
        client_sock.connect(SERVER_ADDRESS)
        print(f"Connected to server at {SERVER_ADDRESS}")

        threading.Thread(target=receive_messages, args=(client_sock,), daemon=True).start()

        while True:
            message = input("Enter message to send (or 'exit' to quit): ")
            if message.lower() == "exit":
                print("Exiting...")
                break
            client_sock.sendall(message.encode())
    except Exception as e:
        print(f"Error: {e}")
    finally:
        client_sock.close()
        print("Connection closed.")


if __name__ == "__main__":
    main()

3. Connect a robot as a client

The server code above works the same way regardless of what connects to it — a laptop, a PLC, or a robot controller that opens a TCP socket. For robot-specific client code, see:

Common mistakes

  • Client and server on different subnets. If the client can’t connect, double-check both IP addresses are in the same range and the port is not blocked by a firewall.
  • Server IP address is wrong or changes. If the server’s PC gets a new IP (e.g. after a reboot or DHCP renewal), every client needs the updated address.
  • Forgetting data.decode() when printing — recv() returns raw bytes, not a string.
  • A client hanging forever on recv() when the server closes unexpectedly — the example above handles this by breaking out of the loop when recv() returns empty data (if not data:), but you may want to add a timeout for production use.

Related


Rewritten and consolidated (Sept 2026) from the original student how-to’s: How to create TCP/IP server that can handle multi-client with python (the client could be laptop and robot).