PYTHON TCP SOCKET PROGTRAMMING: MULTICLIENT & MULTITHREADING

B Manoj
7 min readJan 7, 2021

In this blog, I would like to share the detailed explanation of creating TCP Socket programming for multiple clients with multi threading concept.

First let’s understand the scenario:

  1. We need to create a program that should be able to accept multiple clients on same TCP socket in server side.

2. On each session with the client, both server and client should be able to converse asynchronously (This means there shouldn’t be any rule like first server speaks and then client replies and again server asks some question then client replies …. So on … This means there is a pattern ….. But I want to build a program which shouldn’t depend on pattern and instead converse indefinitely).

I am taking it as granted that viewers already know what is TCP Socket Programming and how it is much better than UDP Sockets.

Now let me provide the whole solution for this project:

Picture demonstrating how multi threading is being used for multiple clients for communication
  • As per this picture, main program runs in main thread and keeps on monitoring for new clients, whenever a new client comes it creates a new thread for that client.
  • Whenever a new thread is created for client, this in turn again creates a separate 2 threads for sending and receiving.
  • Similarly, client only has one main thread which is again split into two different threading for sending and receiving to/from server.

Let’s analyze the server side code :

import socket
import threading
from _thread import *
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.bind((“192.168.43.236”,6666))
ThreadCount = 0
s.listen()
print(‘Socket is listening..’)

def receiving(*arg):
session = arg[0]
while True:
data = session.recv(2048)
print(data.decode())
print(‘\n’)

def sending(*arg):
session = arg[0]
address = arg[1]
while True:
response = input()
response_reframe = ‘192.168.43.236: ‘ + response
session.send(response_reframe.encode())

def multi_threaded_client(session,address):
threading.Thread(target=receiving, args=(session, )).start()
threading.Thread(target=sending, args=(session,address )).start()

while True:
session, address = s.accept()
print(‘Connected to: ‘ + address[0] + ‘:’ + str(address[1]))
start_new_thread(multi_threaded_client,(session,address[0] ))
ThreadCount += 1
print(‘Thread Number: ‘ + str(ThreadCount))

On breaking down the code:

Server Side Code Part1:

s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)// This command is useful in creating a socket for IPv4 address family and working on TCP protocol)

s.bind((“192.168.43.236”,6666)) // After creating a socket, we need to bind that socket to IP address and a particular port number.

ThreadCount = 0 // By tracking this value we can identify number of connections to server.

s.listen() // After creating a socket, we need to intimate to server OS that socket has to listen on particular port number, for that we give this command.
print(‘Socket is listening..’)

Server Side Code Part2:

while True: // This will help us in keep on tracking for new connections from client side

session, address = s.accept() // Once the server starts listening, we also need to tell server OS whether or not to accept connection by using this accept() keyword. //Also, session stores connection related information with the client.//address stores the port number and ip address of clients

print(‘Connected to: ‘ + address[0] + ‘:’ + str(address[1]))

start_new_thread(multi_threaded_client,(session,address[0] )) // This statement tells us that whenever we get a new connection, just a start a new thread for that connection. Also, you can see that parameters like session and address[0] values are send to clients.

ThreadCount += 1
print(‘Thread Number: ‘ + str(ThreadCount)) // These two statements helps us to give a confirmation on new threads that are created whenever new connection with clients is established.

Server Code Part 3:

def multi_threaded_client(session,address): // So, in part 2 of code you have seen that a new thread is allocated for the new connection, but to hold this new connection (Hold in the sense, we can think like ‘to work’), we need some function’s help and that function is multi_threaded_client. Also, you can see that session and IP address (IP address is stored in tuple address[0] while passing from calling side) are also passed to here.

threading.Thread(target=receiving, args=(session, )).start()
threading.Thread(target=sending, args=(session,address )).start() //As per the solution (explained above), I need to create a 2 child threads inside the new connection’s thread so as to communicate with client happens indefinitely. Also, you can see that ‘session’ related arguments are passed to these child threads. Why? Because if we don’t pass them then how will server comes to know to which particular client it has to communicate amongst many clients. So ‘session’ info is very crucial.

Server Code Part 4:

def receiving(*arg): //Catch those arguments from sender side by using *arg
session = arg[0] // Store that session information into a newly created session variable.
while True: // run an infinite loop to converse indefinitely
data = session.recv(2048) //Wait for the data to be received from client’s side. (Here the identity of client is stored in ‘session’ variable)
print(data.decode()) //After receiving the data from client decode it and print it on the terminal
print(‘\n’)

Server Code Part 5:

Explanation of this part of code is same as that of part 4 except to the fact that ‘send’ and ‘receive’ are two different mechanisms.

def sending(*arg):
session = arg[0]
address = arg[1]
while True:
response = input()
response_reframe = ‘192.168.43.236: ‘ + response
session.send(response_reframe.encode())

Let’s analyze the client side code :

import socket
import threading
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) //Creating a Socket
host = ‘192.168.43.236’
port = 6666
s.connect((host, port)) //Connecting to a host
print(‘Waiting for connection response’)

def receiver(): //Function (Child Thread) for receiving the data
while True:
res=s.recv(1024)
print(res.decode())
print(‘\n’)
def sender(): //Function (Child Thread) for sending the data
while True:
msg = input()
msg_reframe = ‘192.168.43.103: ‘ + msg
s.send(msg_reframe.encode())

r_thr=threading.Thread(target=receiver)
s_thr=threading.Thread(target=sender) //Creating both the child threading and assigning the targets as the functions).

r_thr.start()
s_thr.start() // Starting the child threads

Practical Demonstration:

First start the server:

Second start the client1:

Now, check whether the connection is established in server side or not:

Now, there is no rule that server has to start and then client has to reply. Because we made our program to work in ‘full duplex communication’ hence anyone of them can initiate the communication. here, I am starting with server side.

Now, lets check at client side whether the client has received the message or not:

Now, lets send a message from client’s side and check if server has received it or not.

You can see that server has received it.

Let’s also examine if either of them can send multiple messages one after another or not.

Let’s see whether the client has received these two without any hitches or not.

You can see that client machine is able to accept two messages (or even more) at a time without letting server to wait for a response. This is because of multi threading we have achieved this asynchronous communication.

Now let’s connect Client 3:

You can see a new thread is established.

But here there is a problem.

When my first client connects to server, the new threads have already binded with STDIN and STDOUT of SSH shell terminal. Now when I try to connect using my second server, Yes I can connect but the problem is I cannot communicate as the shell terminal’s I/O Channels (STDIN and STDOUT) are already binded up with other threads. Hence, I cannot show the communication happening between server and client2.

As a token of proof, I can show the output of netstat -nct of server to prove you that both the servers were able to connect to my server.

Actually, 233 and 133 are my clients from server perspective.

However, I wouldn’t say that my program is complete. It is still pending …..

The only solution is to let python open a new terminal whenever a new client connects so that each shell can be dedicated each of the clients.

Stay Tuned Guys !!! For lot more interesting blogs !!!

Till Then Bye Bye !!!

--

--