Python: Sockets Programming - Single Thread Server


In previous articles, we built a socket client and made a connection to an Internet based web server. The Internet web server was a socket server listening on port 80. In this article, we discuss how to create a TCP socket server in Python and make it listen to a port we specify. We can then use any socket client, including the one we created in previous chapters, to communicate with the socket server. Note that Python has a dedicated module SocketServer to create socket servers which internally uses the socket module. In this article we do not use the SocketServer module and show how to build a server using the socket module.

The code to run for a socket server is slightly different that than of s socket client. We first have to create a socket object to bind (using socket.bind()) to a specific hostname and port (a hostname/port combination is also called a address) and setup the parent socket to accept inbound connections from clients. When a client connects to the specific hostname and port, a new child socket object is returned which is then used to send and receive data to/from the client.

The parent socket continues to receive incoming requests from new clients and creates a new child socket for each connection. However unless the server has the capability, via multi-threading, to send an receive data simultaneously from each child socket, the server will not send/receive data to/from the new child sockets. In this article, we discuss a simple socket server which does not spawn new threads for a connection. In the next article, we look at a more complex server that spawns new threads for each connection.

If the server does not have the capability to handle multiple client sockets it can restrict the number of clients that can connect to it via a parameter to the socket.listen() function. If we execute socket.listen(), it means that no clients will be allowed to wait and connection will be refused to all but the first connection.

The following is the code for a simple socket server. As for the client, the socket.socket() call returns a socket object (tcpParentSock). The extra calls of tcpParentSock.bind() and tcpParentSock.listen(1) are used to bind the socket to a specific address (host/ipaddress and port combination) and start accepting client connections, respectively. The parameter to the tcpParentSock.listen() call is 1 which will allow one connection to connect and wait in the queue to be processed. However, the waiting connection will not be processed as we do not execute a while loop or run multiple threads to handle more than 1 client. Later in this article, we demonstrate the effect of parameter 1 when trying to make a client connection using the telnet program. Inline comments in the code highlight the purpose of each important line of code.

import socket

# Set the buffer size to receive data
# It is recommended that this be a power of 4
SIZE_BUFFER = 4096

class tcpServerParentSocketClass():
    def __init__(self, host, port):
        self.host = host
        self.port = port

    def start(self):
        # Get a socket object
        tcpParentSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        # Bind socket to local host and port
        try:
            tcpParentSock.bind((self.host, self.port))
        except socket.error as msg:
            print ('Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1])

        # Start listening on socket
        # The parameter value being passed determines the backlog
        # which is the number of connections that are allowed to be held
        # in queue without being refused
        tcpParentSock.listen(1)

        # Wait to accept a connection
        # This call will block till a client connects
        # Once a client connects we will get a new socket object 'newChildSock' which
        # will be used to send an receive data
        # Ideally the new socket should execute in a different thread so that we can
        # serve multiple clients, but in this code we only execute a single thread
        newChildSock, addr = tcpParentSock.accept()
        print('Connected with ' + addr[0] + ':' + str(addr[1]))
        # Send some data to the client using the new Socket
        dataTX = bytes('Welcome ! You have connected to ' + self.host + ' on port ' + str(self.port) + '\r\n','utf-8')
        newChildSock.sendall(dataTX)
        dataTX = bytes('Please press any key in your console, after that server will exit\r\n', 'utf-8')
        newChildSock.sendall(dataTX)
        # The following call will block till 1 byte of data comes in
        dataRX = newChildSock.recv(SIZE_BUFFER)
        dataRXStr = dataRX.decode("utf-8")
        print('Data received from client: ' + dataRXStr)
        print('Closing the child connection now: ' + dataRXStr)
        newChildSock.close()
        print('Closing the parent connection now: ' + dataRXStr)
        tcpParentSock.close() 


# Class to instantiate the threading class and run the thread
class demoSocketServerClass:
    def startSocketServer(self):
        print("----------- startSocketServer")
        # Specify the hostname and port for the server to run
        # Host name of blank "" means the server will run on all available
        # IP addresses of the current machine. We can also give a specific ip address
        # to run only in that port
        socketServer = tcpServerParentSocketClass("", 8001)
        print("Starting Socket Server ...")
        socketServer.start()
        print("Started Socket Server")

# main() function which contain the high level routines
def main():
    dsc = demoSocketServerClass()
    dsc.startSocketServer()

# Call the main() function
main()


We will make a connection to this server using the telnet command. If we run telnet localhost 8001 it will connect to this server. The output in telnet and on the Server side is given below

Telnet:



Output on server side:

----------- startSocketServer
Starting Socket Server ...
Connected with 127.0.0.1:63633
Data received from client: k
Closing the child connection now: k
Closing the parent connection now: k
Started Socket Server

Process finished with exit code 0

Note that we have passed parameter 1 to tcpParentSock.listen(1). This will allow one connection to be serviced by the code beyond line newChildSock, addr = tcpParentSock.accept(), while one more connection is allowed to wait but will never be serviced. If any more connections beyond these two are made they will be refused. We illustrate this using 4 telnet windows below, one will make a connection, 1 will wait for a connection while the other two will be refused connections.




Comments

Popular posts from this blog

Part III: Backpropagation mechanics for a Convolutional Neural Network

Introducing Convolution Neural Networks with a simple architecture

Deriving Pythagoras' theorem using Machine Learning