Sockets in Python: Into the world of Python Network Programming


“Code less ..Achieve more” is the prime philosophy behind the development of all the Very High Level Languages (or VHLL in short). But less no. of lines should not mean reduced flexibility in terms of choosing an approach in solving a problem. Though many of the VHLL or Scripting Languages as they are popularly known, does not keep in mind the flexibility, yet there area 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. The choices are aplenty for the programmer. The choices 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. The first section will cover the basics of the socket module and by the end of the section a simple echo server will be coded. In the second section the echo server would 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:

  1. 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 contains local path such as /var/lock/sock or /tmp/sock.

 

  1. 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 host is a fully qualified Internet host name that can be represented using a string or in the dotted decimal format (or IP address).

  1. 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 the most commonly used. Python supports all of these. My discussion would be limited to the Internet Domain. To create an application that uses TCP/IP sockets following are the steps:

 

  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 later format is used, then the methods could be called without the fully qualified name. I will be using the second format for clarity of the code and ease. Now lets see the various provisions within socket module for the programmers.

 

  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:

 

a. family:

The family parameter specifies in which domain the socket has to be created.  The valid values are AF_UNIX for UNIX domain and AF_INET for internet domain.

b. 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.

c. protocol:

This generally left for default value. The default is 0.

 

So a socket for Internet domain is created thus:

testsocket=socket(AF_INET,SOCK_STREAM)

 

  1. Connecting the Socket:

Sockets thus created can be used on the server-side or client-side. To use the socket as client-socket 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))

 

  1. 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))

  1. Listening and accepting connections:

Once a socket has been named, then it has to be instructed to listen at the given port for incoming requests. This can be done using the listen() method. The listen accepts a no. representing the maximum queued connection. The argument should be atleast 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 done by 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 contains a new socket object and address would contain the address of the client.

 

  1. Transferring data/receiving data:

Data can be transferred using recv() and send() methods of 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 send and the flags. To elucidate  further:

data=raw_input(‘>>’)

testsocket.send(data)

Now that the steps are clear, lets 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 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 preferable mode. The preferable way will be discussed in 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 till the client doesn’t send the null data or ctrl+c. to achieve this again use a while loop 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()

Multi-Threaded Echo Server- Other Approach in Creating a Server:

 

In the above example the uses while loop to service different clients. For elucidations it is ok. But in the real world, it won’t work well. The reason is that more than one client cannot be served simultaneously by just while constructs. To overcome the limitations there are several strategies. One of them is making the server multi-threaded. There are two parts in 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 that particular client is carried out by an independent thread. Thus quick response time can be achieved.

 

  1. Create a handler:

It is the handler where the whole processing goes on. In our case transferring a file.

 

To make the echo server some changes have 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 goes thus:

 

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. This brings us to the end of this section.

 

Parting Thoughts:

  1. 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.
  2. While using threading approach the locking and synchronization issues must be kept in mind.
  3. The security measures must be taken care of when creating FTP kind of 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. Till then…

About these ads

2 thoughts on “Sockets in Python: Into the world of Python Network Programming

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s