In this tutorial, we are going to learn how to create a Python socket server with multiple clients.

Such kinds of servers are usually helpful when you need to build a live chat system generally used for customer support & messaging purposes.

How does it work?

Sockets are commonly used for client and server interaction. Typical system configuration places the server on one machine, with the clients on other machines.

The clients connect to the server, exchange information, and then disconnect.

A socket has a typical flow of events. In a connection-oriented client-to-server model, the socket on the server process waits for requests from a client.

To do this, the server first establishes (binds) an address that clients can use to find the server.

When the address is established, the server waits for clients to request a service. The client-to-server data exchange takes place when a client connects to the server through a socket.

The server performs the client’s request and sends the reply back to the client.

Python socket server with multiple clients

This is a typical flow of events for a connection-oriented socket:

  • The socket() API creates an endpoint for communications and returns a socket descriptor that represents the endpoint.
  • When an application has a socket descriptor, it can bind a unique name to the socket. Servers must bind a name to be accessible from the network.
  • The listen() API indicates a willingness to accept client connection requests. When a listen() API is issued for a socket, and that socket cannot actively initiate connection requests. The listen() API is issued after a socket is allocated with a socket() API and the bind() API binds a name to the socket. A listen() API must be issued before an accept() API is issued.
  • The client application uses a connect() API on a stream socket to establish a connection to the server.
  • The server application uses the accept() API to accept a client connection request. The server must issue the bind() and listen() APIs successfully before it can issue an accept() API.
  • When a connection is established between stream sockets (between client and server), you can use any of the socket API data transfer APIs. Clients and servers have many data transfer APIs from which to choose, such as send(), recv(), read(), write(), and others.
  • When a server or client wants to stop operations, it issues a close() API to release any system resources acquired by the socket.

Creating a Python socket server with multiple clients

To get started, we need to create a multi-threading server that is capable of keeping track of the clients which connect to it

For this, we’ll be using the socket library in Python for the socket server and the threads library for multi-threading support. Let’s import them

import socket
from _thread import *

Now let’s declare the host and port on which we want to communicate with clients.

host = '127.0.0.1'
port = 1234

Now that we have the host and port, let’s create a socket and bind the address to it. We’ll also handle errors and log them to ensure troubleshooting is easy in case things decide to go sideways

ServerSocket = socket.socket()
try:
    ServerSocket.bind((host, port))
except socket.error as e:
    print(str(e))
print(f'Server is listing on the port {port}...')
ServerSocket.listen()

Client Handler

Now that the server is listening to the port. Let’s define how it handles the client. For this, we’ll write a client_handler.

For this example, the server shall replay the same message back to the client.

We‘ll use recv function from the socket library to receive messages from the client. Now to ensure that we’re constantly listening to the client, let’s put a forever loop to it, which breaks if the client says BYE

def client_handler(connection):
    connection.send(str.encode('You are now connected to the replay server...'))
    while True:
        data = connection.recv(2048)
        message = data.decode('utf-8')
        if message == 'BYE':
            break
        reply = f'Server: {message}'
        connection.sendall(str.encode(reply))
    connection.close()

Accepting connections

After the handler is ready we need to ensure that the server accepts new connections from clients and to serve multiple clients, it should open a new thread for each of them. Let’s name this function as accept_connections

def accept_connections(ServerSocket):
    Client, address = ServerSocket.accept()
    print(f'Connected to: {address[0]}:{str(address[1])}')
    start_new_thread(client_handler, (Client, )) 

Wrapping up the Server Side

For the server to keep looking for a new connection, this must be inside a forever loop, which we’ll do while calling the function.

All the parts are now ready and need to be integrated. The following snippet shows the code once all the pieces are put together.

import socket
from _thread import *

host = '127.0.0.1'
port = 1233
ThreadCount = 0

def client_handler(connection):
    connection.send(str.encode('You are now connected to the replay server... Type BYE to stop'))
    while True:
        data = connection.recv(2048)
        message = data.decode('utf-8')
        if message == 'BYE':
            break
        reply = f'Server: {message}'
        connection.sendall(str.encode(reply))
    connection.close()

def accept_connections(ServerSocket):
    Client, address = ServerSocket.accept()
    print('Connected to: ' + address[0] + ':' + str(address[1]))
    start_new_thread(client_handler, (Client, ))

def start_server(host, port):
    ServerSocket = socket.socket()
    try:
        ServerSocket.bind((host, port))
    except socket.error as e:
        print(str(e))
    print(f'Server is listing on the port {port}...')
    ServerSocket.listen()

    while True:
        accept_connections(ServerSocket)
start_server(host, port)

On the Client Side

There are 3 main things that we’ll be doing on the client side

  1. Make a connection
  2. Communicate over the socket
  3. Close the connection

As we are working with sockets. We’ll quickly import sockets. We also have to declare the host and port we want to connect to

import socket
host = '127.0.0.1'
port = 1234

P.S: Please ensure the values are the same as that of the server

Making the connection

Connecting to the socket server is easy, we can simply call the connect function of the socket with information on the host and port we want to connect to. We’ll also handle errors and log it to ensure troubleshooting is easy in case things decide to go sideways.

ClientSocket = socket.socket()
print('Waiting for connection')
try:
    ClientSocket.connect((host, port))
except socket.error as e:
    print(str(e))

Communicating using the socket

Once we have the connection established, we can use the send() and recv() functions to communicate and to remain connected, we’ll use a forever loop as earlier

Response = ClientSocket.recv(2048)
while True:
    message = input('Your message: ')
    ClientSocket.send(str.encode(message))
    reply = ClientSocket.recv(2048)
    decoded_reply = reply.decode('utf-8')
    print(decoded_reply)
    if decoded_reply == 'BYE':
      break

Closing the connection

We can simply call the close() function to close the connection

ClientSocket.close()

Wrapping Up the Client Side

All the parts are now ready and need to be integrated. The following snippet shows the code once all the pieces are put together

import socket
host = '127.0.0.1'
port = 1234

ClientSocket = socket.socket()
print('Waiting for connection')
try:
    ClientSocket.connect((host, port))
except socket.error as e:
    print(str(e))
Response = ClientSocket.recv(2048)
while True:
    Input = input('Your message: ')
    ClientSocket.send(str.encode(Input))
    Response = ClientSocket.recv(2048)
    print(Response.decode('utf-8'))
ClientSocket.close()

That’s all. Our socket server is ready to accept multiple clients and so are our clients. To run and test the code simply save the codes in separate files say server.py and client.py respectively.

Run the server side

python3 server.py

and, To run the client

First, make sure your server is up and running. For each client you want, open a new terminal and run the client code 

python3 client.py

You can find the entire source code here!

Conclusion

In this guide, we have created a Python socket server with multiple clients that replays the message sent by the client. You may experiment and get creative with the use cases further to build something that adds more value for the clients or business. Happy programming!