Asynchronous Socket Programming

Sunday, July 27, 2008

Many a times I got this question from my friends, how to transfer data from one client to another through a server, the solution is very easy using the select call supported in sockets.

To understand the benefit of select call in sockets, one should understand asynchronous socket programming. Assume a case where many clients connect to a server and send data for processing concurrently, then the server has to handle the the clients asynchronously or process the data as and when they are available from any of them.

In synchronous socket programming, the server processes each client sequentially, in this case when it waits for a response/data from a client using the recv call, it blocks or in other words the recv call cannot return until there is some data received from the socket, in a real time scenario, this way of handling clients is inefficient in the sense that all other connected clients need to wait till the server completes processing the current one.

One way handle such a scenario is for the server to accept a connection and fork a child process to handle that client's communication. But this way of handling multiple clients sometimes consumes system resources.

Therefore one needs a more elegant way to asynchronously handle client requests or the ability to read, write from multiple sockets whenever they are ready to be read or written, which is where the select call comes handy.

To explain the use of select system call, I will illustrate a TCP/IP chat Client server program, where the functionalities of the server and the client program were mentioned below.

The chat client and server program were implemented in python for the ease of understanding, note that you need not necessarily use the python client program below to connect to the server, even a telnet appllication can do the same, try to see how the chat client/server works by launching multiple clients in different windows connected to the chat server.

TCP/IP Chat Server:

1. Accepts connection from multiple clients
2. Use select call to get the list of available sockets which are ready to be read.
3. Whenever a client connects, the server notifies all other connected clients of this new connection, in the same way the server notifies all when a client quits or that client connection is lost.
4. The server broadcasts data sent by a client to all other connected clients.

TCP/IP Chat Client:

1. Connects to the server and starts two threads, one to process received data and one for getting data input to be sent to other connected clients through the server.
2. When the client quits (using q or Q) or the server is suddenly down (handle the worst case scenario), the socket is closed and the process exits.
3. Whenever any thread closes the socket connection, it interrupts the main program using the thread.interrupt_main() call, then the main exits.

The syntax used for the select call is as follows

read_sockets,write_sockets,error_sockets = select.select(CONNECTION_LIST,[],[])

The select call returns three lists, the list of sockets which are ready to be read, written and those which caused an error, since we are interested only in the list of sockets which are ready to be read, we use only a single select input parameter.

TCP/IP Chat Server: tcpchatserver.py
#
# tcpchatserver.py
# TCP/IP Chat server
# Author: S.Prasanna
# The server accepts connection from multiple clients and
# broadcasts data sent by a client to all other clients
# which are online (connection active with server)
#

import socket
import select
import string

def broadcast_data (sock, message):
"""Send broadcast message to all clients other than the
server socket and the client socket from which the data is received."""

for socket in CONNECTION_LIST:
if socket != server_socket and socket != sock:
socket.send(message)

if __name__ == "__main__":

# List to keep track of socket descriptors
CONNECTION_LIST=[]
RECV_BUFFER=4096 # Advisable to keep it as an exponent of 2

# Do basic steps for server like create, bind and listening on the socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.bind(("127.0.0.1", 5000))
server_socket.listen(10)

# Add server socket to the list of readable connections
CONNECTION_LIST.append(server_socket)

print "TCP/IP Chat server process started."

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:

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, "Client (%s, %s) connected" % addr)

else:
# Data recieved 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)
except:
broadcast_data(sock, "Client (%s, %s) is offline" % addr)
print "Client (%s, %s) is offline" % addr
sock.close()
CONNECTION_LIST.remove(sock)
continue

if data:
# The client sends some valid data, process it
if data == "q" or data == "Q":
broadcast_data(sock, "Client (%s, %s) quits" % addr)
print "Client (%s, %s) quits" % addr
sock.close()
CONNECTION_LIST.remove(sock)
else:
broadcast_data(sock, data)

server_socket.close()

TCP/IP Chat Client: tcpchatclient.py
#
# tcpchatclient.py
# TCP/IP Chat client
# Author: S.Prasanna
# The client program connects to server and sends data to other connected
# clients through the server
#

import socket
import thread
import sys

HOST = '127.0.0.1' # The remote host
PORT = 5000 # The same port as used by the server
RECV_BUFFER=4096

def recv_data():
"Receive data from other clients connected to server"
while 1:
try:
recv_data = client_socket.recv(RECV_BUFFER)
except:
#Handle the case when server process terminates
print "Server closed connection, thread exiting."
thread.interrupt_main()
break
if not recv_data:
# Recv with no data, server closed connection
print "Server closed connection, thread exiting."
thread.interrupt_main()
break
else:
print "Received data: ", recv_data

def send_data():
"Send data from other clients connected to server"
while 1:
send_data = str(raw_input("Enter data to send (q or Q to quit):"))
if send_data == "q" or send_data == "Q":
client_socket.send(send_data)
thread.interrupt_main()
break
else:
client_socket.send(send_data)

if __name__ == "__main__":

print "*******TCP/IP Chat client program********"
print "Connecting to server at 127.0.0.1:5000"

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client_socket.connect((HOST, PORT))

print "Connected to server at 127.0.0.1:5000"

thread.start_new_thread(recv_data,())
thread.start_new_thread(send_data,())

try:
while 1:
continue
except:
print "Client program quits...."
client_socket.close()
The next time we will see how to handle asynchronous I/O using non-blocking sockets.

20 comments:

srid said...

btw, have you worked with Twisted - http://twistedmatrix.com/

Prasanna Seshadri said...

I know you introduced twisted during the college days, I hacked around it for some time, then my focus changed to other areas in programming, yes its a effective network engine which can be explored.

Anonymous said...

awesome job on this, is there a way to host a server, that can have many 1 on 1 communications? like private messaging? where only 2 or more people can communicate in the server ?

Anonymous said...

and also is whats the code in PYTHON to read and right files? for example usernames and passwords saved to a LOGINS folder?

Prasanna Seshadri said...

For your question 1, yes its possible to make some selected clients to communicate with server, for that all you need to use is basic authentication (username/password), then you can set a session key or something like that which may be sent along with the message throughout that session to validate the authenticated client.

For the second question, file handling in python is way too simple, the following link would be helpful.

1. Python tutorial
http://docs.python.org/tut/node9.html

See the pickle module usage in that section with which you can write any objects/data type into a file.

Anonymous said...

hi
I use this code and works great for me, thanks!!!!

Prasanna Seshadri said...

Glad to hear that this code was useful to you.

Anonymous said...

Do you have any chat program for raw sockets?
That program has server to send a message to client and client responds accordingly.

divya said...

Thank you this helped so much!, i'm doing online chess, so for multi player i need it to send the turn(chance), do you know how to make the client send the chance from one client to my server to another client? and vice versa?

Prasanna Seshadri said...

Thats quite simple, as and when a client connects to a server keep an array of the client socket to identify a client, then send the turn based on the destination address (the client whose turn it should be to make a move).

divya said...

sorry, i did n't get that one if u don't mind please explain it briefly.

pritam said...
This comment has been removed by the author.
Anonymous said...

Hi Prasana ,
Can we have the same implementation in C ?
Please do let me know .

Rgds

aditya said...

Hi Prasana ,
Can we have the same implementation in C ?
Please ......

Garthfield said...

Your indentation is all over the shop fella. It's kind of important with this language.

Prasanna Seshadri said...

Sorry abt that, but hope the link the code will help.

Garthfield said...

The chat client process uses 50% CPU on windows XP. If you connect 2 chat clients they both use 50% each. Introducing a 3rd chat client and the machine starts to crawl.

Windows XP
Python 2.7.1

Garthfield said...

I have found the problem and the solution to this.

http://ted.onflash.org/2005/06/python-cpu-tuning.php

The solution is to import time at the top of the chat client:

import socket
import thread
import sys
import time

Then in the eternal loop at the base of the script add a short delay:

try:
while 1:
time.sleep(0.1)
continue

This brings the CPU usage down to a much more modest 0.

It would be nice to update your script with this and a 1 liner mentioning the reason.

Prasanna Seshadri said...

Excellent, thanks for that suggestion, will update the script with that testing, appreciate your feedback.

ajit said...

Hi
I am new to network programming .i want to know that is it possible to modify TCP segment payload and update new checksum to header?
this funcionality need at sender end?
i want to use python for this.

I will be very glad to any answer.
thanking you.


Copyright © 2016 Prasanna Seshadri, www.prasannatech.net, All Rights Reserved.
No part of the content or this site may be reproduced without prior written permission of the author.