Socket Programming with Multi-threading in Python
Prerequisite : Socket Programming in Python, Multi-threading in Python
We are given a scenario where we need to handle multiple client connections to a server simultaneously. This can be achieved using socket programming along with multi-threading. Socket programming allows two machines to communicate with each other over a network, and multi-threading ensures that the server can handle multiple clients at the same time without blocking. For example, if multiple users try to send messages to a server that simply returns the reversed version of their message, we want all of them to get responses immediately without waiting for others to finish. This can be implemented by combining sockets and threads.
What is a Socket?
A socket in Python is an endpoint in a two-way communication link between two programs running on the network. It is used to send and receive data between a client and a server over a specific port.
What is Multi-threading?
A thread is a lightweight unit of a process. Multi-threading refers to the ability of a CPU (or a single process) to provide multiple threads of execution concurrently. In socket programming, this allows each client to be handled independently in its own thread.
Why Use Multi-threading in Socket Programming?
- To serve multiple clients simultaneously without blocking
- To improve responsiveness of the server
- To separate logic per client (e.g., each client runs its session in parallel)
Multi-threaded Server Code
Below is the server code that uses sockets and multi-threading to handle multiple client connections. Each client gets its own thread, and the server sends back the reversed message received from the client.
import socket
from _thread import start_new_thread
import threading
lock = threading.Lock()
def handle_client(c):
while True:
data = c.recv(1024)
if not data:
print('Bye')
lock.release()
break
c.send(data[::-1])
c.close()
def main():
host = ''
port = 12345
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
s.listen(5)
print("Server running on port", port)
while True:
c, addr = s.accept()
lock.acquire()
print('Connected to:', addr[0], ':', addr[1])
start_new_thread(handle_client, (c,))
if __name__ == '__main__':
main()
Output:
Console Window:
socket binded to port 12345
socket is listening
Connected to : 127.0.0.1 : 11600
Bye
Explanation:
- s.listen(5) server listens for up to 5 queued connections.
- start_new_thread(handle_client, (c,)) starts a new thread for each client.
- lock.acquire() / lock.release() controls access to shared output (like print), avoiding jumbled prints.
Multi-threaded Client Code
This is the client-side script that connects to the server, sends a message, and prints the reversed message received from the server. The client can continue sending messages until they choose to stop.
import socket
def main():
host = '127.0.0.1'
port = 12345
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
msg = "hello from client"
while True:
s.send(msg.encode('ascii'))
data = s.recv(1024)
print('Received from server:', data.decode('ascii'))
ans = input('Do you want to continue (y/n): ')
if ans.lower() != 'y':
break
s.close()
if __name__ == '__main__':
main()
Output:
Received from server: tneilc morf olleh
Do you want to continue (y/n): y
Received from server: tneilc morf olleh
Do you want to continue (y/n): n
Explanation:
- s.connect((host, port)) connects to the server at the given IP and port.
- s.send(msg.encode('ascii')): sends the message to the server in byte format.
- s.recv(1024) receives the reversed message from the server.
- The loop continues until the user inputs n.