Sockets in Python: Into the World of Python Network Programming

Python offers network programmers a variety of options and an excellent degree of flexibility for tackling various situations. This article shows you how to take advantage of that flexibility by using raw sockets to create network oriented applications.

“Code less, achieve more” is the prime philosophy behind the development of all the Very High Level Languages (or VHLL in short). But a fewer number of lines should not mean reduced flexibility in terms of choosing an approach for solving a problem. Though many of the VHLL, or scripting languages as they are popularly known, do not keep flexibility in mind the flexibility, there are a few that have the logic of flexibility and choice as their core.

Python is one of them. This fact is evident if one tries to do network programming in Python. There are plenty of choices for the programmer. These range from low-level sockets or raw-sockets to a completely extensible and functional web server.

In this tutorial I will be discussing how to use raw sockets to create network oriented applications in Python. In the early part, I will cover the basics of the socket module,  and a simple echo server will be coded. Later on, the echo server will be enhanced by making it capable of serving multiple clients using the concepts introduced in the first section.

Sockets and Ports — Doing it the Python way

Sockets and ports form the core of any network oriented application. According to the formal definition a socket is “An endpoint of communication to which a name may be bound.” The concept (as well as implementation) comes from the BSD community. The 4.3BSD implementation defines three domains for the sockets:

Unix Domain/ File-system Domain

The sockets under this domain are used when two or more processes within a system have to communicate with each other. In this domain, the sockets are created within the file system. They are represented as strings that contain local path such as /var/lock/sock or /tmp/sock.

Internet Domain

This domain represents the processes that communicate over the TCP/IP. The sockets created for this domain are represented using a (host, port) tuple. Here a host is a fully qualified Internet host name that can be represented using a string or in the dotted decimal format (IP address).

NS Domain

This domain is the one used by the processes communicating over Xerox protocol which is now obsolete.

Of these only the first two are commonly used. Python supports all of these. My discussion will be limited to the Internet domain. The following are the steps necessary for creating an application that uses TCP/IP sockets:

1. Creating a socket
2. Connecting the socket
3. Binding the socket to an address
4. Listening and accepting connections
5. Transferring data/receiving data.

But before creating a socket, libraries have to be imported. The socket module contains all that is needed to work with sockets. The imports can be done in two ways: import socket or from socket import *. If the first form is used, then to access the methods of socket module, socket.methodname() would have to be used. If the latter form is used, then the methods could be called without the fully qualified name. I will be using the second form for clarity of the code and ease. Now let’s see the various provisions within the socket module for the programmers.

{mospagebreak title=Sockets Step-by-Step}

1. Creating a socket
A socket can be created by making call to the socket() function. The socket() function returns a socket in the domain specified. The parameters to the function are:

  • family: The family parameter specifies in which domain the socket has to be created.  The valid values are AF_UNIX for the UNIX domain and AF_INET for the Internet domain.
  • type: Type defines the type of the protocol to be used. The type can be connection-oriented like TCP or connection-less like UDP. These are defined by the constants SOCK_STREAM for TCP, SOCK_DGRAM for UDP. Other valid parameters are SOCK RAW, SOCK SEQPACKET and SOCK RDM.
  • protocol: This is generally left at the default value. The default is 0.

So a socket for Internet domain is created thus:

testsocket=socket(AF_INET,SOCK_STREAM)
 
2. Connecting the Socket
Sockets thus created can be used on the server-side or client-side. To use the socket on the client side, it needs to be connected to a host. That can be done using the connect() method of the socket object. The connect() method accepts either the host name as the parameter or a tuple containing host name/address and port number as parameter. For example, to connect to a host whose address is 192.168.51.100 and the port number 8080 the statement would be:

testsocket.connect((‘192.168.51.100’,8080))

3. Binding the socket to an address
If the socket has to be used on the server side, then the socket has to be bound to an address and a port, thus naming it. To bind a socket to an address, the bind() method of the socket object has to be used. The valid parameter is a tuple containing the address to which the socket has to be bound and the port at which it has to listen for incoming requests. To use the same socket, i.e. testsocket, on the server side the statement would be:

testsocket.bind((‘192.168.51.100’,8080))

4. Listening and accepting connections
Once a socket has been named, it then must be instructed to listen at the given port for incoming requests. This can be done using the listen() method. The listen accepts a number representing the maximum queued connection. The argument should be at least 1. For example the following code sets the max queued connection to 2:

testsocket.listen(2)

The next thing to be done is to accept the incoming connection requests. This can be accomplished with the accept() function. This function returns a tuple containing a new socket object representing the client and the address of the client. For example:

clientsock,address= testsocket.accept()

In the above statement clientsock would contain a new socket object and address would contain the address of the client.

5. Transferring data/receiving data
Data can be transferred using the recv() and send() methods of the socket object. Socket’s recv() method is used to receive the data send from the server or from the client. The parameters are a buffer size for the data, and flags. The flags parameter is optional. So to receive data the code would be:

buff=1024
testsocket.recv(buff)

To send the data, a call to the send method is in order. The parameters are the data to be sent and the flags. To elucidate  further:

data=raw_input(‘>>’)
testsocket.send(data)

{mospagebreak title=A Simple Echo Server}

Now that the steps are clear, let’s create a simple echo server. First the imports:

from socket import *

Then the constants that defines the host, port, buffer size and the address tuple to be used with bind().

from socket import *
HOST = ‘localhost’
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

Then we create the server side socket and bind it to the host and the port. Then comes the max queue size to 2:

from socket import *
HOST = ‘localhost’
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
serversock = socket(AF_INET, SOCK_STREAM)
serversock.bind(ADDR)
serversock.listen(2)

Now, to make it listen for incoming requests continuously, place the accept() method in a while loop. This is not the most preferred mode. The preferred way will be discussed in the next section:

from socket import *
HOST = ‘localhost’
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
serversock = socket(AF_INET, SOCK_STREAM)
serversock.bind(ADDR)
serversock.listen(2)

while 1:
       print ‘waiting for connection…’
       clientsock, addr = serversock.accept()
       print ‘…connected from:’, addr
       :
       :

Next, receive the data from the client and echo it back. This has to continue until the client doesn’t send the null data or ctrl+c. To achieve this, use a while loop again and then close the connection when done.

from socket import *
HOST = ‘localhost’
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
serversock = socket(AF_INET, SOCK_STREAM)
serversock.bind(ADDR)
serversock.listen(2)

while 1:
       print ‘waiting for connection…’
       clientsock, addr = serversock.accept()
       print ‘…connected from:’, addr

       while 1:
             data = clientsock.recv(BUFSIZ)
             if not data: break
                   clientsock.send(‘echoed’, data)
  
       clientsock.close()

serversock.close()

That’s all for the server. Now for the client. The only exception is that there is no bind(), accept() and listen().

from socket import *

HOST = ‘localhost’
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)

tcpCliSock = socket(AF_INET, SOCK_STREAM)
tcpCliSock.connect(ADDR)

       while 1:
             data = raw_input(‘> ‘)
             if not data: break 
             tcpCliSock.send(data)
            data = tcpCliSock.recv(1024)
             if not data: break 
       print data

tcpCliSock.close()


{mospagebreak title=Multi-Threaded Echo Server – Another Approach for Creating a Server}

The exampl in the previous section uses the while loop to service different clients. For elucidations it is okay. But in the real world, it won’t work well. The reason is that more than one client cannot be served simultaneously with just while constructs. To overcome the limitations there are several strategies. One of them involves making the server multi-threaded. There are two parts to the creation of a multi threaded server:

1. Create threads for each accepted  connections
This is the core of the multi-threaded server. For each accepted connection request, a different thread is created and the serving of that particular client is carried out by an independent thread. Thus quick response times can be achieved.
 
2. Create a handler
It is the handler where the whole processing goes on. In our case, this means  transferring a file.

To make the echo server some changes need to be made. It starts with the accept part as shown below:

from socket import *
from threading import *
HOST = ‘localhost’
PORT = 21567
BUFSIZ = 1024
ADDR = (HOST, PORT)
serversock = socket(AF_INET, SOCK_STREAM)
serversock.bind(ADDR)
serversock.listen(2)
while 1:
       print ‘waiting for connection…’
       clientsock, addr = serversock.accept()
       print ‘…connected from:’, addr
       thread.start_new_thread(handler, (clientsock,
addr))
 
serversock.close()

After accepting the request, a new thread is created for the client. This is done for each connection request. The logic of handling the client is defined within the handler, which looks like this:

from socket import *
from threading import *
def handler(clientsock,addr):
       while 1:
             data = clientsock.recv(BUFSIZ)
             if not data: break
                   clientsock.send(‘echoed:..’, data)
  
       clientsock.close()
if __name__==’__main__’:
       HOST = ‘localhost’
       PORT = 21567
       BUFSIZ = 1024
       ADDR = (HOST, PORT)
       serversock = socket(AF_INET, SOCK_STREAM)
       serversock.bind(ADDR)
       serversock.listen(2)

       while 1:
             print ‘waiting for connection…’
             clientsock, addr = serversock.accept()
             print ‘…connected from:’, addr
             thread.start_new_thread(handler, (clientsock, addr))
#some other cleanup code if necessary

The handler has to be defined before calling it. The handler contains the same code that was contained in the inner while loop previously. This example is not optimized. But it serves the purpose of providing a different approach for serving multiple clients. 

Parting Thoughts

Here are a few points to keep in mind:

  • The low level sockets can be mixed and matched with other modules such as threads and forks to create a server capable of serving multiple clients simultaneously.
  • While using the threading approach, the locking and synchronization issues must be kept in mind.
  • Security measures must be taken when creating FTP servers.

This brings us to the end of this discussion. In the introductory section I mentioned flexibility as one of the core aspects of Python. Ability to work with low-level sockets is one of them. But at the other end of the spectrum are the pre-built yet extensible web servers. These will be discussed in the near future.

[gp-comments width="770" linklove="off" ]

antalya escort bayan antalya escort bayan Antalya escort diyarbakir escort