Search icon CANCEL
Subscription
0
Cart icon
Your Cart (0 item)
Close icon
You have no products in your basket yet
Arrow left icon
Explore Products
Best Sellers
New Releases
Books
Videos
Audiobooks
Learning Hub
Conferences
Free Learning
Arrow right icon
Arrow up icon
GO TO TOP
Raspberry Pi: Amazing Projects from Scratch

You're reading from   Raspberry Pi: Amazing Projects from Scratch Explore the powers of Raspberry Pi and build your very own projects right out of the box

Arrow left icon
Product type Course
Published in Sep 2016
Publisher
ISBN-13 9781787128491
Length 593 pages
Edition 1st Edition
Arrow right icon
Authors (4):
Arrow left icon
Matthew Poole Matthew Poole
Author Profile Icon Matthew Poole
Matthew Poole
Ashwin Pajankar Ashwin Pajankar
Author Profile Icon Ashwin Pajankar
Ashwin Pajankar
Richard Grimmett Richard Grimmett
Author Profile Icon Richard Grimmett
Richard Grimmett
Arush Kakkar Arush Kakkar
Author Profile Icon Arush Kakkar
Arush Kakkar
Arrow right icon
View More author details
Toc

Chapter 14. Network Programming in Python with the Pi

A network socket is an endpoint of a connection across computer networks. Nowadays, almost all communication between computers and distinct networks is based on the Internet Protocol, which uses sockets as a basis of communication. A socket is an abstract reference that a local program can pass to the network API to make use of a connection. Sockets are internally often represented in network programming APIs simply as integers, which identify which connection to use. In brief, we will be covering the following topics in this chapter:

  • The basics of sockets
  • The difference between TCP and UDP sockets
  • UDP sockets
  • TCP sockets
  • The Telnet program
  • A chat application

The basics of sockets

A socket API is an application programming interface, provided by the operating system (OS), that allows application programs to initiate, control, and use network sockets programmatically for communication. Internet socket APIs are usually based on the Berkeley sockets standard. In the Berkeley sockets standard, sockets are a form of file descriptor, adhering to the Unix philosophy that everything is a file. Thus, we can read, write, open, and close sockets in the same way as we do files. In inter-process communications, each end will have its own socket, but these may use different socket-programming APIs. However, they are abstracted by the network protocol.

A socket address is a combination of an Internet Protocol (IP) address and a port number. Internet sockets deliver and receive data packets to and from the appropriate application process or thread. On a computer with an IP address, every network-related program or utility will have its own unique socket or set of sockets. This ensures that the incoming data is redirected to the correct application.

The difference between TCP and UDP

There are two types of protocols in the IP suite. They are Transmission Control Protocol (TCP) and User Datagram Protocol (UDP). TCP is a connection-oriented IP which means that once a connection is established, data can be sent in a bidirectional manner. UDP is a much simpler, connectionless Internet protocol. Multiple messages are sent as packets in chunks using UDP. Let's distinguish between the two with clear points, as follows:

TCP

UDP

TCP is a connection-oriented protocol.

UDP is a connectionless protocol.

Using this mode, a message makes its way across the Internet from one computer and network to another. This is connection based.

UDP is also a protocol used in message transport or transfer. It is not a connection-based protocol. A program using UDP can send a lot of packets to another, and that would be the end of the relationship.

TCP is suited to applications that require high reliability, and transmission time is relatively less critical.

UDP is suitable for applications that need fast, efficient transmission, such as games and live-streaming videos.

Protocols utilizing TCP include HTTP, HTTPS, FTP, SMTP, and Telnet.

Protocols utilizing UDP include DNS, DHCP, TFTP, SNMP, RIP, and VoIP.

Data is read as a stream of bytes; no distinguishing indications are transmitted to signal message segment boundaries.

Packets are sent individually and are checked for integrity only on arrival. Packets have headers and endpoints.

TCP performs error checking.

UDP performs error checking. However, it has no recovery options for missed or corrupt packets.

The data transferred remains intact and arrives in the same order (known as in-order) in which it was sent.

There is no guarantee that the messages or packets sent will reach at all or will be in the same order as transmitted.

The architecture and programming of UDP sockets

Here is the architecture of a UDP client-server system:

The architecture and programming of UDP sockets

UDP is an Internet protocol. Just like its counterpart TCP (which we will discuss soon), UDP is a protocol for the transfer of packets from one host to another. However, as seen in the diagram, it has some important differences from TCP. Unlike TCP, UDP is connectionless and is not a stream-oriented protocol. This means a UDP server just catches incoming packets from any and many hosts without establishing a reliable and dedicated connection for the transfer of data between processes.

A UDP socket is created as follows in Python:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

SOCK_DGRAM specifies a UDP socket.

Sending and receiving data with UDP

As UDP sockets are connectionless sockets, communication is done with the socket functions sendto() and recvfrom(). These functions do not require a socket to be connected to another peer explicitly. They just send and receive directly to and from a given IP address.

UDP servers and NCAT

The simplest form of a UDP server in Python is as follows:

import socket
port = 5000
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", port))
print "waiting on port:", port
while 1:
    data, addr = s.recvfrom(1024)
    print data

Instead of having a listen() function, a UDP server has to open a socket and wait to receive incoming packets. As is evident from the code snippet, there is no listen() or accept() function. Save the above code and execute it from a terminal and then connect to it using the NCAT utility. NCAT is an alternative to Telnet, and it is more powerful than Telnet and packed with more features. Run the program and, in another terminal window, use NCAT. Here is the output of the execution of the program and NCAT:

pi@raspberrypi ~/book/chapter14 $ nc localhost 5000 -u -v
Connection to localhost 5000 port [udp/*] succeeded!

Hello
Ok

The -u flag in the command indicates the UDP protocol. The message we send should be displayed on the server terminal.

An echo server using Python UDP sockets

Here is the code for an echo server in Python:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
# Datagram (udp) socket
try :
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    print 'Socket created'
except socket.error, msg :
    print 'Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
 
 
# Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
#now keep talking with the client
while 1:
    # receive data from client (data, addr)
    d = s.recvfrom(1024)
    data = d[0]
    addr = d[1]
     
    if not data: 
        break
     
    reply = 'OK...' + data
     
    s.sendto(reply , addr)
    print 'Message[' + addr[0] + ':' + str(addr[1]) + '] - ' + data.strip()
     
s.close()

This program will start a UDP server process on the mentioned port (in our case, it's 8888). Save the program and run it in a terminal. To test the program, open another terminal and use the NCAT utility to connect to this server, as follows:

pi@raspberrypi ~/book/chapter14 $ nc -vv localhost 8888 -u
Connection to localhost 8888 port [udp/*] succeeded!
OK...XOK...XOK...XOK...XOK...X
OK...
Hello
OK...Hello
How are you?
OK...How are you?

Use NCAT again to send messages to the UDP server, and the UDP server will reply back with OK... prefixed to the message.

The server process terminal also displays the details about the client connected, as follows:

$ python prog2.py 
Socket created
Socket bind complete
Message[127.0.0.1:46622] - Hello
Message[127.0.0.1:46622] - How are you?

It is important to note that unlike a TCP server, a UDP server can handle multiple clients directly as there is no explicit connection with a client (hence connectionless). It can receive from any client and send a reply to it. No threads are required as we do in TCP servers.

A UDP client

The code for a UDP client is as follows:

import socket   #for sockets
import sys  #for exit

# create dgram udp socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

host = 'localhost';
port = 8888;

while(1) :
    msg = raw_input('Enter message to send : ')

    try :
        #Set the whole string
        s.sendto(msg, (host, port))

        # receive data from client (data, addr)
        d = s.recvfrom(1024)
        reply = d[0]
        addr = d[1]

        print 'Server reply : ' + reply

    except socket.error, msg:
        print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

The client will connect to the UDP server and exchange messages as follows:

pi@raspberrypi ~/book/chapter14 $ python prog3.py 
Enter message to send : Hello
Server reply : OK...Hello
Enter message to send : How are you
Server reply : OK...How are you
Enter message to send : Ok
Server reply : OK...Ok
Enter message to send : 

The programs for the UDP protocol are simple to code as there are no explicit connections from the UDP clients to the UDP server.

In the next subsection, we will learn about TCP sockets.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

The architecture and programming of UDP sockets

Here is the architecture of a UDP client-server system:

The architecture and programming of UDP sockets

UDP is an Internet protocol. Just like its counterpart TCP (which we will discuss soon), UDP is a protocol for the transfer of packets from one host to another. However, as seen in the diagram, it has some important differences from TCP. Unlike TCP, UDP is connectionless and is not a stream-oriented protocol. This means a UDP server just catches incoming packets from any and many hosts without establishing a reliable and dedicated connection for the transfer of data between processes.

A UDP socket is created as follows in Python:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

SOCK_DGRAM specifies a UDP socket.

Sending and receiving data with UDP

As UDP sockets are connectionless sockets, communication is done with the socket functions sendto() and recvfrom(). These functions do not require a socket to be connected to another peer explicitly. They just send and receive directly to and from a given IP address.

UDP servers and NCAT

The simplest form of a UDP server in Python is as follows:

import socket
port = 5000
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", port))
print "waiting on port:", port
while 1:
    data, addr = s.recvfrom(1024)
    print data

Instead of having a listen() function, a UDP server has to open a socket and wait to receive incoming packets. As is evident from the code snippet, there is no listen() or accept() function. Save the above code and execute it from a terminal and then connect to it using the NCAT utility. NCAT is an alternative to Telnet, and it is more powerful than Telnet and packed with more features. Run the program and, in another terminal window, use NCAT. Here is the output of the execution of the program and NCAT:

pi@raspberrypi ~/book/chapter14 $ nc localhost 5000 -u -v
Connection to localhost 5000 port [udp/*] succeeded!

Hello
Ok

The -u flag in the command indicates the UDP protocol. The message we send should be displayed on the server terminal.

An echo server using Python UDP sockets

Here is the code for an echo server in Python:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
# Datagram (udp) socket
try :
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    print 'Socket created'
except socket.error, msg :
    print 'Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
 
 
# Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
#now keep talking with the client
while 1:
    # receive data from client (data, addr)
    d = s.recvfrom(1024)
    data = d[0]
    addr = d[1]
     
    if not data: 
        break
     
    reply = 'OK...' + data
     
    s.sendto(reply , addr)
    print 'Message[' + addr[0] + ':' + str(addr[1]) + '] - ' + data.strip()
     
s.close()

This program will start a UDP server process on the mentioned port (in our case, it's 8888). Save the program and run it in a terminal. To test the program, open another terminal and use the NCAT utility to connect to this server, as follows:

pi@raspberrypi ~/book/chapter14 $ nc -vv localhost 8888 -u
Connection to localhost 8888 port [udp/*] succeeded!
OK...XOK...XOK...XOK...XOK...X
OK...
Hello
OK...Hello
How are you?
OK...How are you?

Use NCAT again to send messages to the UDP server, and the UDP server will reply back with OK... prefixed to the message.

The server process terminal also displays the details about the client connected, as follows:

$ python prog2.py 
Socket created
Socket bind complete
Message[127.0.0.1:46622] - Hello
Message[127.0.0.1:46622] - How are you?

It is important to note that unlike a TCP server, a UDP server can handle multiple clients directly as there is no explicit connection with a client (hence connectionless). It can receive from any client and send a reply to it. No threads are required as we do in TCP servers.

A UDP client

The code for a UDP client is as follows:

import socket   #for sockets
import sys  #for exit

# create dgram udp socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

host = 'localhost';
port = 8888;

while(1) :
    msg = raw_input('Enter message to send : ')

    try :
        #Set the whole string
        s.sendto(msg, (host, port))

        # receive data from client (data, addr)
        d = s.recvfrom(1024)
        reply = d[0]
        addr = d[1]

        print 'Server reply : ' + reply

    except socket.error, msg:
        print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

The client will connect to the UDP server and exchange messages as follows:

pi@raspberrypi ~/book/chapter14 $ python prog3.py 
Enter message to send : Hello
Server reply : OK...Hello
Enter message to send : How are you
Server reply : OK...How are you
Enter message to send : Ok
Server reply : OK...Ok
Enter message to send : 

The programs for the UDP protocol are simple to code as there are no explicit connections from the UDP clients to the UDP server.

In the next subsection, we will learn about TCP sockets.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Sending and receiving data with UDP

As UDP sockets are connectionless sockets, communication is done with the socket functions sendto() and recvfrom(). These functions do not require a socket to be connected to another peer explicitly. They just send and receive directly to and from a given IP address.

UDP servers and NCAT

The simplest form of a UDP server in Python is as follows:

import socket
port = 5000
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", port))
print "waiting on port:", port
while 1:
    data, addr = s.recvfrom(1024)
    print data

Instead of having a listen() function, a UDP server has to open a socket and wait to receive incoming packets. As is evident from the code snippet, there is no listen() or accept() function. Save the above code and execute it from a terminal and then connect to it using the NCAT utility. NCAT is an alternative to Telnet, and it is more powerful than Telnet and packed with more features. Run the program and, in another terminal window, use NCAT. Here is the output of the execution of the program and NCAT:

pi@raspberrypi ~/book/chapter14 $ nc localhost 5000 -u -v
Connection to localhost 5000 port [udp/*] succeeded!

Hello
Ok

The -u flag in the command indicates the UDP protocol. The message we send should be displayed on the server terminal.

An echo server using Python UDP sockets

Here is the code for an echo server in Python:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
# Datagram (udp) socket
try :
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    print 'Socket created'
except socket.error, msg :
    print 'Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
 
 
# Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
#now keep talking with the client
while 1:
    # receive data from client (data, addr)
    d = s.recvfrom(1024)
    data = d[0]
    addr = d[1]
     
    if not data: 
        break
     
    reply = 'OK...' + data
     
    s.sendto(reply , addr)
    print 'Message[' + addr[0] + ':' + str(addr[1]) + '] - ' + data.strip()
     
s.close()

This program will start a UDP server process on the mentioned port (in our case, it's 8888). Save the program and run it in a terminal. To test the program, open another terminal and use the NCAT utility to connect to this server, as follows:

pi@raspberrypi ~/book/chapter14 $ nc -vv localhost 8888 -u
Connection to localhost 8888 port [udp/*] succeeded!
OK...XOK...XOK...XOK...XOK...X
OK...
Hello
OK...Hello
How are you?
OK...How are you?

Use NCAT again to send messages to the UDP server, and the UDP server will reply back with OK... prefixed to the message.

The server process terminal also displays the details about the client connected, as follows:

$ python prog2.py 
Socket created
Socket bind complete
Message[127.0.0.1:46622] - Hello
Message[127.0.0.1:46622] - How are you?

It is important to note that unlike a TCP server, a UDP server can handle multiple clients directly as there is no explicit connection with a client (hence connectionless). It can receive from any client and send a reply to it. No threads are required as we do in TCP servers.

A UDP client

The code for a UDP client is as follows:

import socket   #for sockets
import sys  #for exit

# create dgram udp socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

host = 'localhost';
port = 8888;

while(1) :
    msg = raw_input('Enter message to send : ')

    try :
        #Set the whole string
        s.sendto(msg, (host, port))

        # receive data from client (data, addr)
        d = s.recvfrom(1024)
        reply = d[0]
        addr = d[1]

        print 'Server reply : ' + reply

    except socket.error, msg:
        print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

The client will connect to the UDP server and exchange messages as follows:

pi@raspberrypi ~/book/chapter14 $ python prog3.py 
Enter message to send : Hello
Server reply : OK...Hello
Enter message to send : How are you
Server reply : OK...How are you
Enter message to send : Ok
Server reply : OK...Ok
Enter message to send : 

The programs for the UDP protocol are simple to code as there are no explicit connections from the UDP clients to the UDP server.

In the next subsection, we will learn about TCP sockets.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

UDP servers and NCAT

The simplest form of a UDP server in Python is as follows:

import socket
port = 5000
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", port))
print "waiting on port:", port
while 1:
    data, addr = s.recvfrom(1024)
    print data

Instead of having a listen() function, a UDP server has to open a socket and wait to receive incoming packets. As is evident from the code snippet, there is no listen() or accept() function. Save the above code and execute it from a terminal and then connect to it using the NCAT utility. NCAT is an alternative to Telnet, and it is more powerful than Telnet and packed with more features. Run the program and, in another terminal window, use NCAT. Here is the output of the execution of the program and NCAT:

pi@raspberrypi ~/book/chapter14 $ nc localhost 5000 -u -v
Connection to localhost 5000 port [udp/*] succeeded!

Hello
Ok

The -u flag in the command indicates the UDP protocol. The message we send should be displayed on the server terminal.

An echo server using Python UDP sockets

Here is the code for an echo server in Python:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
# Datagram (udp) socket
try :
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    print 'Socket created'
except socket.error, msg :
    print 'Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
 
 
# Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
#now keep talking with the client
while 1:
    # receive data from client (data, addr)
    d = s.recvfrom(1024)
    data = d[0]
    addr = d[1]
     
    if not data: 
        break
     
    reply = 'OK...' + data
     
    s.sendto(reply , addr)
    print 'Message[' + addr[0] + ':' + str(addr[1]) + '] - ' + data.strip()
     
s.close()

This program will start a UDP server process on the mentioned port (in our case, it's 8888). Save the program and run it in a terminal. To test the program, open another terminal and use the NCAT utility to connect to this server, as follows:

pi@raspberrypi ~/book/chapter14 $ nc -vv localhost 8888 -u
Connection to localhost 8888 port [udp/*] succeeded!
OK...XOK...XOK...XOK...XOK...X
OK...
Hello
OK...Hello
How are you?
OK...How are you?

Use NCAT again to send messages to the UDP server, and the UDP server will reply back with OK... prefixed to the message.

The server process terminal also displays the details about the client connected, as follows:

$ python prog2.py 
Socket created
Socket bind complete
Message[127.0.0.1:46622] - Hello
Message[127.0.0.1:46622] - How are you?

It is important to note that unlike a TCP server, a UDP server can handle multiple clients directly as there is no explicit connection with a client (hence connectionless). It can receive from any client and send a reply to it. No threads are required as we do in TCP servers.

A UDP client

The code for a UDP client is as follows:

import socket   #for sockets
import sys  #for exit

# create dgram udp socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

host = 'localhost';
port = 8888;

while(1) :
    msg = raw_input('Enter message to send : ')

    try :
        #Set the whole string
        s.sendto(msg, (host, port))

        # receive data from client (data, addr)
        d = s.recvfrom(1024)
        reply = d[0]
        addr = d[1]

        print 'Server reply : ' + reply

    except socket.error, msg:
        print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

The client will connect to the UDP server and exchange messages as follows:

pi@raspberrypi ~/book/chapter14 $ python prog3.py 
Enter message to send : Hello
Server reply : OK...Hello
Enter message to send : How are you
Server reply : OK...How are you
Enter message to send : Ok
Server reply : OK...Ok
Enter message to send : 

The programs for the UDP protocol are simple to code as there are no explicit connections from the UDP clients to the UDP server.

In the next subsection, we will learn about TCP sockets.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

An echo server using Python UDP sockets

Here is the code for an echo server in Python:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
# Datagram (udp) socket
try :
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    print 'Socket created'
except socket.error, msg :
    print 'Failed to create socket. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
 
 
# Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
#now keep talking with the client
while 1:
    # receive data from client (data, addr)
    d = s.recvfrom(1024)
    data = d[0]
    addr = d[1]
     
    if not data: 
        break
     
    reply = 'OK...' + data
     
    s.sendto(reply , addr)
    print 'Message[' + addr[0] + ':' + str(addr[1]) + '] - ' + data.strip()
     
s.close()

This program will start a UDP server process on the mentioned port (in our case, it's 8888). Save the program and run it in a terminal. To test the program, open another terminal and use the NCAT utility to connect to this server, as follows:

pi@raspberrypi ~/book/chapter14 $ nc -vv localhost 8888 -u
Connection to localhost 8888 port [udp/*] succeeded!
OK...XOK...XOK...XOK...XOK...X
OK...
Hello
OK...Hello
How are you?
OK...How are you?

Use NCAT again to send messages to the UDP server, and the UDP server will reply back with OK... prefixed to the message.

The server process terminal also displays the details about the client connected, as follows:

$ python prog2.py 
Socket created
Socket bind complete
Message[127.0.0.1:46622] - Hello
Message[127.0.0.1:46622] - How are you?

It is important to note that unlike a TCP server, a UDP server can handle multiple clients directly as there is no explicit connection with a client (hence connectionless). It can receive from any client and send a reply to it. No threads are required as we do in TCP servers.

A UDP client

The code for a UDP client is as follows:

import socket   #for sockets
import sys  #for exit

# create dgram udp socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

host = 'localhost';
port = 8888;

while(1) :
    msg = raw_input('Enter message to send : ')

    try :
        #Set the whole string
        s.sendto(msg, (host, port))

        # receive data from client (data, addr)
        d = s.recvfrom(1024)
        reply = d[0]
        addr = d[1]

        print 'Server reply : ' + reply

    except socket.error, msg:
        print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

The client will connect to the UDP server and exchange messages as follows:

pi@raspberrypi ~/book/chapter14 $ python prog3.py 
Enter message to send : Hello
Server reply : OK...Hello
Enter message to send : How are you
Server reply : OK...How are you
Enter message to send : Ok
Server reply : OK...Ok
Enter message to send : 

The programs for the UDP protocol are simple to code as there are no explicit connections from the UDP clients to the UDP server.

In the next subsection, we will learn about TCP sockets.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

A UDP client

The code for a UDP client is as follows:

import socket   #for sockets
import sys  #for exit

# create dgram udp socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

host = 'localhost';
port = 8888;

while(1) :
    msg = raw_input('Enter message to send : ')

    try :
        #Set the whole string
        s.sendto(msg, (host, port))

        # receive data from client (data, addr)
        d = s.recvfrom(1024)
        reply = d[0]
        addr = d[1]

        print 'Server reply : ' + reply

    except socket.error, msg:
        print 'Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        sys.exit()

The client will connect to the UDP server and exchange messages as follows:

pi@raspberrypi ~/book/chapter14 $ python prog3.py 
Enter message to send : Hello
Server reply : OK...Hello
Enter message to send : How are you
Server reply : OK...How are you
Enter message to send : Ok
Server reply : OK...Ok
Enter message to send : 

The programs for the UDP protocol are simple to code as there are no explicit connections from the UDP clients to the UDP server.

In the next subsection, we will learn about TCP sockets.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

The architecture of TCP sockets

The following is a diagram of the TCP client-server architecture:

The architecture of TCP sockets

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Creating a TCP socket

Here is an example of creating a TCP socket:

import socket–– #for sockets
–
create an AF_INET, STREAM socket (TCP)
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
–
print 'Socket Created'

The socket.socket()function creates a socket and returns a socket descriptor, which can be used in other socket-related functions.

This code will create a socket with the following properties:

  • Address family: AF_INET (this is for IP version 4, or IPv4)
  • Type: SOCK_STREAM (this specifies a connection-oriented protocol, that is, TCP)

If any of the socket functions fail, then Python throws an exception called socket.error, which must be caught as follows:

import socket–– #for sockets
import sys– for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'

In this way, we have created a TCP socket successfully. We can connect to a server, for example, http://www.google.com, using this socket.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Connecting to a server with a TCP socket

We can connect to a remote server on a certain port number. We need two things for this: the IP address of the remote server we are connecting to and a port number to connect to. We will use the IP address of https://www.google.com as a sample in the following code.

First, we need to get the IP address of the remote host or URL, since before connecting to a remote host, its IP address is required. In Python, obtaining the IP address is quite simple:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip

Now that we have the IP address of the remote host or URL, we can connect to it on a certain port using the connect() function:

import socket–– #for sockets
import sys– #for exit
–
try:
––––create an AF_INET, STREAM socket (TCP)
––––s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
––––print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
––––sys.exit();
–
print 'Socket Created'
–
host = 'www.google.com'
port = 80
–
try:
––––remote_ip = socket.gethostbyname( host )
–
except socket.gaierror:
––––could not resolve
––––print 'Hostname could not be resolved. Exiting'
––––sys.exit()
–––––
print 'Ip address of ' + host + ' is ' + remote_ip
–
Connect to remote server
s.connect((remote_ip , port))
–
print 'Socket Connected to ' + host + ' on ip ' + remote_ip

Run the program and notice that its output in the terminal is as follows:

pi@raspberrypi ~/book/chapter14 $ python prog4.py 
Socket Created
Ip address of www.google.com is 74.125.236.83
Socket Connected to www.google.com on ip 74.125.236.83

It creates a TCP socket and then connects to a remote host. If we try connecting to a port different from port 80, then we should not be able to connect, which indicates that the port is not open for any connections. This logic can be used to build a port scanner.

Note

The concept of connections applies to SOCK_STREAM/TCP type of sockets. A connection means an explicit or reliable stream or pipeline of data such that there can be multiple such streams and each can have communication of its own. Think of this as a pipe that is not interfered with by data from other pipes. Another important property of stream connections is that the packets have an order or sequence; hence, they are always sent, arrive, and are processed in order.

Other sockets, such as UDP, ICMP, and ARP, don't have the concept of an explicit connection or pipeline. These are connectionless communications, as we have seen with an example in the case of UDP. This means that we keep sending or receiving packets from anybody and everybody.

The sendall()function will send all the data. Let's send some data to https://www.google.com. The code for it is as follows:

import socket   #for sockets
import sys  #for exit
 
try:
    #create an AF_INET, STREAM socket (TCP)
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error, msg:
    print 'Failed to create socket. Error code: ' + str(msg[0]) + ' , Error message : ' + msg[1]
    sys.exit();
 
print 'Socket Created'
 
host = 'www.google.com'
port = 80
 
try:
    remote_ip = socket.gethostbyname( host )
 
except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()
     
print 'Ip address of ' + host + ' is ' + remote_ip
 
#Connect to remote server
s.connect((remote_ip , port))
 
print 'Socket Connected to ' + host + ' on ip ' + remote_ip
 
#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"
 
try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()
 
print 'Message send successfully'

In this example, we first connect to an IP address and then send the string message GET / HTTP/1.1\r\n\r\n to it. The message is actually an HTTP command to fetch the main page of the website.

Now that we have sent some data, it's time to receive a reply from the server. So let's do it.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Receiving data from the server

The recv()function is used to receive data on a socket. In the following example, we will send a message and receive a reply from the server with Python:

import socket   #for sockets
import sys  #for exit

#create an INET, STREAMing socket
try:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
except socket.error:
    print 'Failed to create socket'
    sys.exit()

print 'Socket Created'

host = 'www.google.com';
port = 80;

try:
    remote_ip = socket.gethostbyname( host )

except socket.gaierror:
    #could not resolve
    print 'Hostname could not be resolved. Exiting'
    sys.exit()

#Connect to remote server
s.connect((remote_ip , port))

print 'Socket Connected to ' + host + ' on ip ' + remote_ip

#Send some data to remote server
message = "GET / HTTP/1.1\r\n\r\n"

try :
    #Set the whole string
    s.sendall(message)
except socket.error:
    #Send failed
    print 'Send failed'
    sys.exit()

print 'Message send successfully'

#Now receive data
reply = s.recv(4096)

print reply

Your web browser also does the same thing when you use it to open www.google.com.

This socket activity represents a client socket. A client is a system that connects to a remote host to fetch the required data.

The other type of socket activity is called a server system. A server is a system that uses sockets to receive incoming connections and provide them with the required data. It is just the opposite of the client system. So, https://www.google.com is a server system and a web browser is a client system. To be more specific, https://www.google.com is an HTTP server and a web browser is an HTTP client.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Programming socket servers

Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:

  1. Open a socket
  2. Bind to an address (and port)
  3. Listen for incoming client connection requests
  4. Accept connections
  5. Receive/send data from/to clients

We already know to open a socket. So, the next thing to learn will be how to bind it.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Binding a socket

The bind()function can be used to bind a socket to a particular IP address and port. It needs a sockaddr_in structure similar to the connect() function:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'

Now that the binding is done, it's time to make the socket listen to incoming connection requests. We bind a socket to a particular IP address and a certain port number. By doing this, we ensure that all incoming data packets directed towards this port number are received by the server application.

Also, there cannot be more than one socket bound to the same port.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Listening for incoming connections

After binding a socket to a particular port, the next thing we need to do is listen for incoming connections. For this, we need to switch the socket to listening mode. The socket_listen()function is used to put the socket in listening mode. To accomplish this, we need to add the following line after the bind code:

s.listen(10)
print 'Socket now listening'

The parameter of the listen()function is called the backlog. It is used to control the number of incoming connections that are kept waiting if the program using that port is already busy. So, by mentioning 10, we mean that if 10 connections are already waiting to be processed, then the eleventh new connection request shall be rejected. This will be clearer after checking socket_accept().

Here is the code to do that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
#display client information
print 'Connected with ' + addr[0] + ':' + str(addr[1])

Run the program. It should show you the following:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening

So now, this program is waiting for incoming client connections on port 8888. Don't close this program; ensure that you keep it running.

Now, a client can connect to the server on this port. We will use the Telnet client for testing this. Open a terminal and type this:

$ telnet localhost 8888

It will immediately show the following:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Connection closed by foreign host.
pi@raspberrypi ~/book/chapter14 $ 

And this is what the server will show:

pi@raspberrypi ~/book/chapter14 $ python prog7.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:59954

So now, we can see that the client is connected to the server. We accepted an incoming connection but closed it immediately. There are lots of tasks that can be accomplished after an incoming connection is established. The connection was established between two hosts for the purpose of communication. So let's reply to the client.

The sendall()function can be used to send something to the socket of the incoming connection, and the client should be able to receive it. Here is the code for that:

import socket
import sys
 
HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port
 
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'
 
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()
     
print 'Socket bind complete'
 
s.listen(10)
print 'Socket now listening'
 
#wait to accept a connection - blocking call
conn, addr = s.accept()
 
print 'Connected with ' + addr[0] + ':' + str(addr[1])
 
#now keep talking with the client
data = conn.recv(1024)
conn.sendall(data)
 
conn.close()
s.close()

Run this code in a terminal window and connect to this server using Telnet from another terminal; you should be able to see this:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
happy
Connection closed by foreign host.

So, the client (Telnet) received a reply from the server.

We can see that the connection is closed immediately after this, simply because the server program terminates immediately after accepting the request and responding with the reply. A server process is supposed to be running all the time; the simplest way to accomplish this is to iterate the accept method in a loop so that it can receive incoming connections all the time.

So, a live server will always be up and running. The code for this is as follows:

import socket
import sys

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 5000 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

s.listen(10)
print 'Socket now listening'

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    data = conn.recv(1024)
    reply = 'OK...' + data
    if not data:
        break

    conn.sendall(reply)

conn.close()
s.close()

Now run this server program in a terminal, and open three other terminals.

From each of the three terminals, perform a Telnet to the server port.

Each of the Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 5000
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
happy
OK .. happy
Connection closed by foreign host.

The server terminal will show the following:

pi@raspberrypi ~/book/chapter14 $ python prog9.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60225
Connected with 127.0.0.1:60237
Connected with 127.0.0.1:60239

So now, the server is up and the Telnet terminals are also connected to it. Now, terminate the server program. All Telnet terminals will show Connection closed by foreign host.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Handling multiple connections

To handle every connection, we need separate handling code to run along with the main server thread, which accepts incoming connection requests. One way to achieve this is to use threads. The main server program accepts an incoming connection request and provisions a new thread to handle communication for the connection, and then, the server goes back to accept more incoming connection requests.

This is the required code:

import socket
import sys
from thread import *

HOST = ''   # Symbolic name meaning all available interfaces
PORT = 8888 # Arbitrary non-privileged port

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print 'Socket created'

#Bind socket to local host and port
try:
    s.bind((HOST, PORT))
except socket.error , msg:
    print 'Bind failed. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
    sys.exit()

print 'Socket bind complete'

#Start listening on socket
s.listen(10)
print 'Socket now listening'

#Function for handling connections. This will be used to create threads
def clientthread(conn):
    #Sending message to connected client
    conn.send('Welcome to the server. Type something and hit enter\n') #send only takes string

    #infinite loop so that function do not terminate and thread do not end.
    while True:

        #Receiving from client
        data = conn.recv(1024)
        reply = 'OK...' + data
        if not data:
            break

        conn.sendall(reply)

    #came out of loop
    conn.close()

#now keep talking with the client
while 1:
    #wait to accept a connection - blocking call
    conn, addr = s.accept()
    print 'Connected with ' + addr[0] + ':' + str(addr[1])

    #start new thread takes 1st argument as a function name to be run, second is the tuple of arguments to the function.
    start_new_thread(clientthread ,(conn,))

s.close()

Run this server code and open three terminals like before. Now, the server will create a thread for each client connecting to it.

The Telnet terminals will show output as follows:

pi@raspberrypi ~/book/chapter14 $ telnet localhost 8888
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome to the server. Type something and hit enter
hi
OK...hi
asd
OK...asd
cv
OK...cv

The server terminal will look like this:

pi@raspberrypi ~/book/chapter14 $ python prog10.py
Socket created
Socket bind complete
Socket now listening
Connected with 127.0.0.1:60730
Connected with 127.0.0.1:60731

This connection handler takes the input from the client and replies with the same.

Looking back

By now, we have learned the basics of socket programming in Python. When testing these programs, if you face the following error, simply change the port number, and the server will run fine:

Bind failed. Error Code : 98 Message Address already in use

A Telnet client in Python

The Telnet client is a simple command-line program that is used to connect to socket servers and exchange messages. The following is an example of how to use Telnet to connect to https://www.google.com and fetch the homepage:

$ telnet www.google.com 80

This command will connect to www.google.com on port 80.

$ telnet www.google.com 80
Trying 74.125.236.69...
Connected to www.google.com.
Escape character is '^]'

Now that it is connected, the Telnet command can take user input and send it to the respective server, and whatever the server replies will be displayed in the terminal. For example, send the HTTP GET command in the following format and hit Enter twice:

GET / HTTP/1.1

Sending this will generate a response from the server. Now we will make a similar Telnet program. The program is simple: we will implement a program that takes user input and fetches results from the remote server in parallel using the threads. One thread will keep receiving messages from the server and another will keep taking in user input. But there is another way to do this apart from threads: the select() function. This function allows you to monitor multiple sockets or streams for readability and will generate an event if any of the sockets are ready.

The code for it is as follows:

import socket, select, string, sys

#main function
if __name__ == "__main__":

    if(len(sys.argv) < 3) :
        sys.exit()

    host = sys.argv[1]
    port = int(sys.argv[2])

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    # connect to remote host
    try :
        s.connect((host, port))
    except :
        print 'Unable to connect'
        sys.exit()

    print 'Connected to remote host'

    while 1:
        socket_list = [sys.stdin, s]

        # Get the list sockets which are readable
        read_sockets, write_sockets, error_sockets = select.select(socket_list , [], [])

        for sock in read_sockets:
            #incoming message from remote server
            if sock == s:
                data = sock.recv(4096)
                if not data :
                    print 'Connection closed'
                    sys.exit()
                else :
                    #print data
                    sys.stdout.write(data)

            #user entered a message
            else :
                msg = sys.stdin.readline()
                s.send(msg)

The execution of this program is shown here. It connects to the remote host google.com.

$ python telnet.py google.com 80
Connected to remote host

Once connected, it shows the appropriate connected message. Once the message is displayed, type in some message to send to the remote server. Type the same GET message and send it by hitting Enter twice. A response will be generated.

A chat program

In the previous section, we went through the basics of creating a socket server and client in Python. In this section, we will write a chat application in Python that is powered by Python sockets.

The chat application we are going to make will be a common chat room rather than a peer-to-peer chat. So this means that multiple chat users can connect to the chat server and exchange messages. Every message is broadcast to every connected chat user.

The chat server

The chat server performs the following tasks:

  • It accepts multiple incoming connections for the client.
  • It reads incoming messages from each client and broadcasts them to all the other connected clients.

The following is the code for the chat server:

import socket, select

#Function to broadcast chat messages to all connected clients
def broadcast_data (sock, message):
    #Do not send the message to master socket and the client who has send us the message
    for socket in CONNECTION_LIST:
        if socket != server_socket and socket != sock :
            try :
                socket.send(message)
            except :
                # broken socket connection may be, chat client pressed ctrl+c for example
                socket.close()
                CONNECTION_LIST.remove(socket)

if __name__ == "__main__":

    # List to keep track of socket descriptors
    CONNECTION_LIST = []
    RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
    PORT = 5000

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # this has no effect, why ?
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind(("0.0.0.0", PORT))
    server_socket.listen(10)

    # Add server socket to the list of readable connections
    CONNECTION_LIST.append(server_socket)

    print "Chat server started on port " + str(PORT)

    while 1:
        # Get the list sockets which are ready to be read through select
        read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])

        for sock in read_sockets:
            #New connection
            if sock == server_socket:
                # Handle the case in which there is a new connection recieved through server_socket
                sockfd, addr = server_socket.accept()
                CONNECTION_LIST.append(sockfd)
                print "Client (%s, %s) connected" % addr

                broadcast_data(sockfd, "[%s:%s] entered room\n" % addr)

            #Some incoming message from a client
            else:
                # Data received from client, process it
                try:
                    #In Windows, sometimes when a TCP program closes abruptly,
                    # a "Connection reset by peer" exception will be thrown
                    data = sock.recv(RECV_BUFFER)
                    if data:
                        broadcast_data(sock, "\r" + '<' + str(sock.getpeername()) + '> ' + data)

                except:
                    broadcast_data(sock, "Client (%s, %s) is offline" % addr)
                    print "Client (%s, %s) is offline" % addr
                    sock.close()
                    CONNECTION_LIST.remove(sock)
                    continue

    server_socket.close()

In this code, the server program opens up port 5000 to listen for incoming connections from the clients. The chat client must connect to the same port. We can change the port number if we want by specifying another number that is not used by any other program or process.

The server handles multiple chat clients with multiplexing based on the select() function. The select() function monitors all the client sockets and the master socket for any readable activity. If any of the client sockets are readable, then it means that one of the chat clients has sent a message to the server.

When the select() function returns, read_sockets will be an array consisting of all socket descriptors that are readable. When the master socket is readable, the server will accept the new connection. If any of the client sockets is readable, the server will read the message and broadcast it back to all the other clients except the one who sent the message. If the broadcast function fails to send the message to any of the clients, the client is presumed to be disconnected, the connection is closed, and the corresponding socket is removed from the connections list.

The chat client

Now, let's code the chat client that will connect to the chat server and exchange the messages. The chat client is based on the Telnet program in Python, which we have already worked on. It connects to a remote server and exchanges messages.

The chat client performs the following tasks:

  • It listens for incoming messages from the server.
  • It checks for user input, in case the user types in a message and then sends it to the chat server to which it is connected.

The client has to actually listen for server messages and user input simultaneously. To accomplish this, we can use the select() function. The select() function can monitor multiple sockets or file descriptors simultaneously for activity. When a message from the server arrives on the connected socket, it is readable, and when the user types a message from the keyboard and hits Enter, the stdin stream is readable.

So, the select() function has to monitor two streams: the first is the socket that is connected to the remote web server, and the second is stdin or terminal input stream from the keyboard. The select() function waits until some activity happens. So, after calling the select() function, the function itself will return only when either the server socket receives a message or the user enters a message using the keyboard. If nothing happens, it keeps on waiting.

We can create an array of the stdin file descriptor that is available from the sys module and the server sockets. Then, we call the select() function, passing it the list. The select() function returns a list of arrays that are readable, writable, or have an error.

Here is the Python code that implements the previous logic using the select() function as follows:

import socket, select, string, sys

def prompt() :
    sys.stdout.write('<You> ')
    sys.stdout.flush()

#main function
if __name__ == "__main__":

    if(len(sys.argv) < 3) :
        print 'Usage : python telnet.py hostname port'
        sys.exit()

    host = sys.argv[1]
    port = int(sys.argv[2])

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    # connect to remote host
    try :
        s.connect((host, port))
    except :
        print 'Unable to connect'
        sys.exit()

    print 'Connected to remote host. Start sending messages'
    prompt()

    while 1:
        socket_list = [sys.stdin, s]

        # Get the list sockets which are readable
        read_sockets, write_sockets, error_sockets = select.select(socket_list , [], [])

        for sock in read_sockets:
            #incoming message from remote server
            if sock == s:
                data = sock.recv(4096)

                if not data :
                    print '\nDisconnected from chat server'
                    sys.exit()
                else :
                    #print data
                    sys.stdout.write(data)
                    prompt()

            #user entered a message
            else :
                msg = sys.stdin.readline()
                s.send(msg)
                prompt()

Execute the chat client from multiple consoles as follows:

$ python telnet.py localhost 5000
Connected to remote host. Start sending messages
<You> hello
<You> I am fine
<('127.0.0.1', 38378)> ok good
<You>
on another console
<You> [127.0.0.1:39339] entered room
<('127.0.0.1', 39339)> hello
<('127.0.0.1', 39339)> I am fine
<You> ok good

In this way, the messages sent by one client can be seen on the terminal of the other client.

The chat server

The chat server performs the following tasks:

  • It accepts multiple incoming connections for the client.
  • It reads incoming messages from each client and broadcasts them to all the other connected clients.

The following is the code for the chat server:

import socket, select

#Function to broadcast chat messages to all connected clients
def broadcast_data (sock, message):
    #Do not send the message to master socket and the client who has send us the message
    for socket in CONNECTION_LIST:
        if socket != server_socket and socket != sock :
            try :
                socket.send(message)
            except :
                # broken socket connection may be, chat client pressed ctrl+c for example
                socket.close()
                CONNECTION_LIST.remove(socket)

if __name__ == "__main__":

    # List to keep track of socket descriptors
    CONNECTION_LIST = []
    RECV_BUFFER = 4096 # Advisable to keep it as an exponent of 2
    PORT = 5000

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # this has no effect, why ?
    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    server_socket.bind(("0.0.0.0", PORT))
    server_socket.listen(10)

    # Add server socket to the list of readable connections
    CONNECTION_LIST.append(server_socket)

    print "Chat server started on port " + str(PORT)

    while 1:
        # Get the list sockets which are ready to be read through select
        read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])

        for sock in read_sockets:
            #New connection
            if sock == server_socket:
                # Handle the case in which there is a new connection recieved through server_socket
                sockfd, addr = server_socket.accept()
                CONNECTION_LIST.append(sockfd)
                print "Client (%s, %s) connected" % addr

                broadcast_data(sockfd, "[%s:%s] entered room\n" % addr)

            #Some incoming message from a client
            else:
                # Data received from client, process it
                try:
                    #In Windows, sometimes when a TCP program closes abruptly,
                    # a "Connection reset by peer" exception will be thrown
                    data = sock.recv(RECV_BUFFER)
                    if data:
                        broadcast_data(sock, "\r" + '<' + str(sock.getpeername()) + '> ' + data)

                except:
                    broadcast_data(sock, "Client (%s, %s) is offline" % addr)
                    print "Client (%s, %s) is offline" % addr
                    sock.close()
                    CONNECTION_LIST.remove(sock)
                    continue

    server_socket.close()

In this code, the server program opens up port 5000 to listen for incoming connections from the clients. The chat client must connect to the same port. We can change the port number if we want by specifying another number that is not used by any other program or process.

The server handles multiple chat clients with multiplexing based on the select() function. The select() function monitors all the client sockets and the master socket for any readable activity. If any of the client sockets are readable, then it means that one of the chat clients has sent a message to the server.

When the select() function returns, read_sockets will be an array consisting of all socket descriptors that are readable. When the master socket is readable, the server will accept the new connection. If any of the client sockets is readable, the server will read the message and broadcast it back to all the other clients except the one who sent the message. If the broadcast function fails to send the message to any of the clients, the client is presumed to be disconnected, the connection is closed, and the corresponding socket is removed from the connections list.

The chat client

Now, let's code the chat client that will connect to the chat server and exchange the messages. The chat client is based on the Telnet program in Python, which we have already worked on. It connects to a remote server and exchanges messages.

The chat client performs the following tasks:

  • It listens for incoming messages from the server.
  • It checks for user input, in case the user types in a message and then sends it to the chat server to which it is connected.

The client has to actually listen for server messages and user input simultaneously. To accomplish this, we can use the select() function. The select() function can monitor multiple sockets or file descriptors simultaneously for activity. When a message from the server arrives on the connected socket, it is readable, and when the user types a message from the keyboard and hits Enter, the stdin stream is readable.

So, the select() function has to monitor two streams: the first is the socket that is connected to the remote web server, and the second is stdin or terminal input stream from the keyboard. The select() function waits until some activity happens. So, after calling the select() function, the function itself will return only when either the server socket receives a message or the user enters a message using the keyboard. If nothing happens, it keeps on waiting.

We can create an array of the stdin file descriptor that is available from the sys module and the server sockets. Then, we call the select() function, passing it the list. The select() function returns a list of arrays that are readable, writable, or have an error.

Here is the Python code that implements the previous logic using the select() function as follows:

import socket, select, string, sys

def prompt() :
    sys.stdout.write('<You> ')
    sys.stdout.flush()

#main function
if __name__ == "__main__":

    if(len(sys.argv) < 3) :
        print 'Usage : python telnet.py hostname port'
        sys.exit()

    host = sys.argv[1]
    port = int(sys.argv[2])

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    # connect to remote host
    try :
        s.connect((host, port))
    except :
        print 'Unable to connect'
        sys.exit()

    print 'Connected to remote host. Start sending messages'
    prompt()

    while 1:
        socket_list = [sys.stdin, s]

        # Get the list sockets which are readable
        read_sockets, write_sockets, error_sockets = select.select(socket_list , [], [])

        for sock in read_sockets:
            #incoming message from remote server
            if sock == s:
                data = sock.recv(4096)

                if not data :
                    print '\nDisconnected from chat server'
                    sys.exit()
                else :
                    #print data
                    sys.stdout.write(data)
                    prompt()

            #user entered a message
            else :
                msg = sys.stdin.readline()
                s.send(msg)
                prompt()

Execute the chat client from multiple consoles as follows:

$ python telnet.py localhost 5000
Connected to remote host. Start sending messages
<You> hello
<You> I am fine
<('127.0.0.1', 38378)> ok good
<You>
on another console
<You> [127.0.0.1:39339] entered room
<('127.0.0.1', 39339)> hello
<('127.0.0.1', 39339)> I am fine
<You> ok good

In this way, the messages sent by one client can be seen on the terminal of the other client.

The chat client

Now, let's code the chat client that will connect to the chat server and exchange the messages. The chat client is based on the Telnet program in Python, which we have already worked on. It connects to a remote server and exchanges messages.

The chat client performs the following tasks:

  • It listens for incoming messages from the server.
  • It checks for user input, in case the user types in a message and then sends it to the chat server to which it is connected.

The client has to actually listen for server messages and user input simultaneously. To accomplish this, we can use the select() function. The select() function can monitor multiple sockets or file descriptors simultaneously for activity. When a message from the server arrives on the connected socket, it is readable, and when the user types a message from the keyboard and hits Enter, the stdin stream is readable.

So, the select() function has to monitor two streams: the first is the socket that is connected to the remote web server, and the second is stdin or terminal input stream from the keyboard. The select() function waits until some activity happens. So, after calling the select() function, the function itself will return only when either the server socket receives a message or the user enters a message using the keyboard. If nothing happens, it keeps on waiting.

We can create an array of the stdin file descriptor that is available from the sys module and the server sockets. Then, we call the select() function, passing it the list. The select() function returns a list of arrays that are readable, writable, or have an error.

Here is the Python code that implements the previous logic using the select() function as follows:

import socket, select, string, sys

def prompt() :
    sys.stdout.write('<You> ')
    sys.stdout.flush()

#main function
if __name__ == "__main__":

    if(len(sys.argv) < 3) :
        print 'Usage : python telnet.py hostname port'
        sys.exit()

    host = sys.argv[1]
    port = int(sys.argv[2])

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.settimeout(2)

    # connect to remote host
    try :
        s.connect((host, port))
    except :
        print 'Unable to connect'
        sys.exit()

    print 'Connected to remote host. Start sending messages'
    prompt()

    while 1:
        socket_list = [sys.stdin, s]

        # Get the list sockets which are readable
        read_sockets, write_sockets, error_sockets = select.select(socket_list , [], [])

        for sock in read_sockets:
            #incoming message from remote server
            if sock == s:
                data = sock.recv(4096)

                if not data :
                    print '\nDisconnected from chat server'
                    sys.exit()
                else :
                    #print data
                    sys.stdout.write(data)
                    prompt()

            #user entered a message
            else :
                msg = sys.stdin.readline()
                s.send(msg)
                prompt()

Execute the chat client from multiple consoles as follows:

$ python telnet.py localhost 5000
Connected to remote host. Start sending messages
<You> hello
<You> I am fine
<('127.0.0.1', 38378)> ok good
<You>
on another console
<You> [127.0.0.1:39339] entered room
<('127.0.0.1', 39339)> hello
<('127.0.0.1', 39339)> I am fine
<You> ok good

In this way, the messages sent by one client can be seen on the terminal of the other client.

Exercise

Additionally, you can learn more about raw sockets in Python from the following links:

Summary

In this chapter, we learned about the concepts required to understand the transfer of data between two or more machines, in which the sender is termed as a server and the receiver is termed as the client. We also learned the basics of establishing a connection between a server and a client.

We built both UDP and TCP socket programs using Python and learned how to perform tasks such as connecting to a server, sending messages, receiving messages, and so on. Finally, we combined all that we learned to create a simple chat program that would allow two users to communicate with each other. We can now build our own instant messaging service!

Now that we are at the end of the book, take some time to go over the projects that you have successfully built. We have completed some projects, ranging from the beginner level to the advanced level, and learned a lot about the intricacies of working with the Raspberry Pi and all its parallel concepts firsthand. Starting from the introduction of the Raspberry Pi to projects such as motion detection from images and intruder detection using the concepts of the Internet of Things, we have covered almost everything that you would require to build some interesting projects of your own.

We hope this inspires you to keep on building fun projects that are not only interesting to set up and cool to watch but also useful to the world in general. Keep on hacking!

You have been reading a chapter from
Raspberry Pi: Amazing Projects from Scratch
Published in: Sep 2016
Publisher:
ISBN-13: 9781787128491
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at $19.99/month. Cancel anytime
Banner background image