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:
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:
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:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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:
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:
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:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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 following is a diagram of the TCP client-server architecture:
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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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 following is a diagram of the TCP client-server architecture:
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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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 following is a diagram of the TCP client-server architecture:
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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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 following is a diagram of the TCP client-server architecture:
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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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:
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:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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.
Now, we move on to learning how to program socket servers. Servers basically perform the following sequence of tasks:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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:
- Open a socket
- Bind to an address (and port)
- Listen for incoming client connection requests
- Accept connections
- 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.
References
The code and definitions in this chapter have been referred to from a few online sources, as follows:
- http://www.binarytides.com/python-socket-programming-tutorial
- http://www.binarytides.com/python-socket-server-code-example
- http://www.binarytides.com/code-chat-application-server-client-sockets-python
- http://www.binarytides.com/code-telnet-client-sockets-python
- http://www.binarytides.com/programming-udp-sockets-in-python
- http://www.binarytides.com/socket-programming-c-linux-tutorial/
- http://www.diffen.com/difference/TCP_vs_UDP
- http://eprints.uthm.edu.my/7531/1/FAYAD_MOHAMMED_MOHAMMED_GHAWBAR_24.pdf
- http://www.cs.dartmouth.edu/~campbell/cs60/UDPsockets.jpg
- http://www.cs.dartmouth.edu/~campbell/cs60/TCPsockets.jpg
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!