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 clients asynchronously.
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.
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.
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.
The client and server Chat program were implemented in python for the ease of understanding. Try to see how the chat client/server works by launching multiple clients in different windows connected to the chat server.
Server.py
# 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=[] # 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(4096) 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()
client.py
# The client program connects to server and sends data to other connected # clients through the server import socket import thread import sys def recv_data(): "Receive data from other clients connected to server" while 1: try: recv_data = client_socket.recv(4096) 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(('127.0.0.1', 5000)) 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()
Select with blocking sockets? Really?
ReplyDeleteThanks for the article.....it solved my problem..i will spread the word
ReplyDelete