Sockets modules
Network sockets is a way to talk to other computers using standard Unix file descriptors, which allow communication between two different processes on the same or different machines. A socket is almost similar to a low-level file descriptor, because commands such as read()
and write()
also work with sockets as they do with files.
Python has two basic sockets modules:
- Socket: The standard BSD sockets API.
- SocketServer: A server-centric module that defines classes for handling synchronous network requests that simplify the development of network servers.
Socket
The socket
module has almost everything you need to build a socket server or client. In the case of Python, the socket
returns an object to which the socket methods can be applied.
Methods in socket module
The socket module has the following class methods:
socket.socket(family, type)
: Create and return a new socket objectsocket.getfqdn(name)
: Convert a string IP address to a fully qualified domain namesocket.gethostbyname(hostname)
: Resolve a hostname to an IP address
Instance methods require a socket instance returned from socket
. The socket
module has the following instance methods:
sock.bind( (address, port) )
: Bind the socket to the address and portsock.accept()
: Return a client socket with peer address informationsock.listen(backlog)
: Place the socket into the listening statesock.connect( (address, port) )
: Connect the socket to the defined host and portsock.recv( bufferLength[, flags] )
: Receive data from the socket, up tobuflen
(maximum bytes to receive) bytessock.recvfrom( bufferLength[, flags] )
: Receive data from the socket, up tobuflen
bytes, also returning the remote host and port from which the data camesock.send( data[, flags] )
: Send data through the socketsock.sendall( data[, flags] )
: Send data through the socket, and continues to send data until either all data has been sent or an error occurredsock.close()
: Close the socketsock.getsockopt( lvl, optname )
: Get the value for the specified socket optionsock.setsockopt( lvl, optname, val )
: Set the value for the specified socket option
Creating a socket
A socket can be created by making a call to the class method socket()
in the socket
module. This will return a socket in the domain specified. The parameters to the method are as follows:
- Address family: Python supports three address families.
- AF_INET: Used for IP version 4 or IPv4 Internet addressing.
- AF_INET6: Used for IPv6 Internet addressing.
- AF_UNIX: Used for UNIX domain sockets (UDS).
- Socket type: Usually, socket type can be either
SOCK_DGRAM
for User Datagram Protocol (UDP) orSOCK_STREAM
for Transmission Control Protocol (TCP).SOCK_RAW
is used to create raw sockets. - Protocol: Generally left at the default value. Default value is 0.
The following is an example for creating a socket:
import socket #Imported sockets module import sys try: #Create an AF_INET (IPv4), STREAM socket (TCP) tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, e: print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit(); print 'Success!'
Connecting to a server and sending data
The socket created can be used in both server-side or client-side.
The connect()
method of socket object is used to connect the client to a host. This instance method accepts either the host name or a tuple, which contains the host name/address and port number as a parameter.
We can rewrite the preceding code to send a message to the server as follows:
import socket #Imported sockets module import sys TCP_IP = '127.0.0.1' TCP_PORT = 8090 #Reserve a port BUFFER_SIZE = 1024 MESSAGE_TO_SERVER = "Hello, World!" try: #Create an AF_INET (IPv4), STREAM socket (TCP) tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, e: print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit(); tcp_socket.connect((TCP_IP, TCP_PORT)) try : #Sending message tcp_socket.send(MESSAGE_TO_SERVER) except socket.error, e: print 'Error occurred while sending data to server. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit() print 'Message to the server send successfully'
Receiving data
We need a server to receive data. To use a socket on the server side, the bind()
method of the socket
object binds a socket to an address. It takes a tuple as the input parameter, which contains the address to the socket and the port to listen for incoming requests. The listen()
method puts the socket into listening mode and the method accept()
waits for an incoming connection. The listen()
method accepts a parameter representing the maximum number of queued connections. So by specifying this parameter to 3
, it means that if three connections are waiting to process, then the fourth connection will be rejected:
import socket #Imported sockets module TCP_IP = '127.0.0.1' TCP_PORT = 8090 BUFFER_SIZE = 1024 #Normally use 1024, to get fast response from the server use small size try: #Create an AF_INET (IPv4), STREAM socket (TCP) tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, e: print 'Error occurred while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit(); tcp_socket.bind((TCP_IP, TCP_PORT)) # Listen for incoming connections (max queued connections: 2) tcp_socket.listen(2) print 'Listening..' #Waits for incoming connection (blocking call) connection, address = tcp_socket.accept() print 'Connected with:', address
Method accept()
will return an active connection between the server and client. Data can be read from the connection using the recv()
method, and can be transmitted using sendall()
:
data = connection.recv(BUFFER_SIZE) print "Message from client:", data connection.sendall("Thanks for connecting") # response for the message from client connection.close()
It would be better to keep the server live by putting socket_accept
in a loop, as follows:
#keep server alive while True: connection, address = tcp_socket.accept() print 'Client connected:', address data = connection.recv(BUFFER_SIZE) print "Message from client:", data connection.sendall("Thanks for connecting") #Echo the message from client
Save this to server.py
and start the server as follows in a terminal:
$ python server.py
Then server terminal might look like the following:
Now we can modify the client script to receive a response from the server:
import socket #Imported sockets module import sys TCP_IP = '127.0.0.1' TCP_PORT = 8090 # Reserve a port BUFFER_SIZE = 1024 MESSAGE_TO_SERVER = "Hello, World!" try: #Create an AF_INET (IPv4), STREAM socket (TCP) tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, e: print 'Error occured while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit(); tcp_socket.connect((TCP_IP, TCP_PORT)) try : #Sending message tcp_socket.send(MESSAGE_TO_SERVER) except socket.error, e: print 'Error occurred while sending data to server. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit() print 'Message to the server send successfully' data = tcp_socket.recv(BUFFER_SIZE) tcp_socket.close() #Close the socket when done print "Response from server:", data
Save this to client.py
and run. Please make sure the server script is running. The client-side terminal might look like the following:
Handling multiple connections
In the previous example, we used the while loop to handle different clients; this can only interact with one client at a time. To make the server interact with multiple clients, we have to use multi-threading. When the main
program accepts a connection, it creates a new thread to handle communication for this connection, and then goes back to accept more connections.
We can use the threads module to create thread handlers for each connection that the server accepts.
start_new_thread()
takes two arguments:
- A function name to be run
- A tuple of arguments to that function
Let's see how we can rewrite the preceding example with threads:
import socket #Imported sockets module import sys from thread import * TCP_IP = '127.0.0.1' TCP_PORT = 8090 # Reserve a port try: #create an AF_INET (IPv4), STREAM socket (TCP) tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) except socket.error, e: print 'Error occured while creating socket. Error code: ' + str(e[0]) + ' , Error message : ' + e[1] sys.exit(); #Bind socket to host and port tcp_socket.bind((TCP_IP, TCP_PORT)) tcp_socket.listen(10) print 'Listening..' #Function for handling connections. Used to create threads def ClientConnectionHandler(connection): BUFFER_SIZE = 1024 #Sending message to client connection.send('Welcome to the server') #infinite loop to keep the thread alive. while True: #Receiving data from client data = connection.recv(BUFFER_SIZE) reply = 'Data received:' + data if not data: break connection.sendall(reply) #Exiting loop connection.close() #keep server alive always (infinite loop) while True: connection, address = tcp_socket.accept() print 'Client connected:', address start_new_thread(ClientConnectionHandler ,(connection,)) tcp_socket.close()
Tip
For more details on socket modules, go to https://docs.python.org/2.7/library/socket.html.
SocketServer
SocketServer
is an interesting module, which is a framework for creating network servers. It has pre-defined classes for handling synchronous requests using TCP, UDP, UNIX streams, and UNIX datagrams. We can also create forking and threading versions of each type of server using the mix-in classes. In many cases, you can simply use one of the existing server classes. Five different server classes defined in SocketServer
module are as follows:
BaseServer
: Defines the API, not used directlyTCPServer
: Uses TCP/IP socketsUDPServer
: Uses datagram socketsUnixStreamServer
: Unix-domain stream socketsUnixDatagramServer
: Unix-domain datagram sockets
To construct a server with this module, we have to pass the address to listen (a tuple consisting of the address and port number) and a request handler class. Request handlers will receive incoming requests and decide what action to take. This class must have a method, which overrides any of the following RequestHandler
methods; mostly, we can simply override a handle()
method. A new instance of this class is created for each and every request:
setup()
: Called before thehandle()
method to prepare the request handler for the requesthandle()
: Parses the incoming requests, processes the data, and responds to the requestsfinish()
: Called after thehandle()
method to clean up anything created duringsetup()
Simple server with the SocketServer module
The following script shows how we can use SocketServer
to create a simple echo server:
import SocketServer #Imported SocketServer module #The RequestHandler class for our server. class TCPRequestHandler( SocketServer.StreamRequestHandler ): def handle( self ): self.data = self.request.recv(1024).strip() print "{} wrote:".format(self.client_address[0]) print self.data #Sending the same data self.request.sendall(self.data) #Create the server, binding to localhost on port 8090 server = SocketServer.TCPServer( ("", 8090), TCPRequestHandler ) #Activate the server; this will keep running untile we interrupt server.serve_forever()
The first line of the script imports the SocketServer
module:
import SocketServer
Then we created a request handler that inherits the SocketServer.StreamRequestHandler
class and overrides the handle()
method to handle the requests for the server. The method handle()
receives the data, prints it, and then responds the same to the client:
class TCPRequestHandler( SocketServer.StreamRequestHandler ): def handle( self ): self.data = self.request.recv(1024).strip() print "{} wrote:".format(self.client_address[0]) print self.data # sending the same data self.request.sendall(self.data)
This request handler class is instantiated for every request to the server. This server is created using the SocketServer.TCPServer
class, where we provide the address to which the server will be bound and request the handler class. It will return a TCPServer
object. Finally, we called the serve_forever()
method to start the server and handle requests until we send an explicit shutdown()
request (keyboard interrupt):
tcp_server = SocketServer.TCPServer( ("", 8090), TCPRequestHandler ) tcp_server.serve_forever()
Tip
For more details on Socket module, go to http://xahlee.info/python_doc_2.7.6/library/socketserver.html.