In a previous tutorial we learnt how to do basic socket programming in python. The tutorial explained how to code a socket server and client in python using low level socket api. Check out that tutorial if you are not through on the basics of socket programming in python.
To recap, sockets are virtual endpoints of a communication channel that takes place between 2 programs or processes on the same or different machines. This is more simply called network communication and sockets are the fundamental things behind network applications. For example when you open google.com in your browser, your browser creates a socket and connects to google.com server. There is a socket on google.com server also that accepts the connection and sends your browser the webpage that you see.
Socket Servers in python
In this post we shall learn how to write a simple socket server in python. This has already been covered in the previous tutorial. In this post we shall learn few more things about programming server sockets like handling multiple connections with the select method.
So lets take a look at a simple python server first. The things to do are, create a socket, bind it to a port and then accept connections on the socket.
1. Create socket with socket.socket function 2. Bind socket to address+port with socket.bind function 3. Put the socket in listening mode with socket.listen function 3. Accept connection with socket.accept function
Now lets code it up.
''' Simple socket server using threads ''' 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' #Bind socket to local host and port try: s.bind((HOST, PORT)) except socket.error as msg: print 'Bind failed. Error Code : ' + str(msg) + ' Message ' + msg sys.exit() print 'Socket bind complete' #Start listening on socket 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 + ':' + str(addr) s.close()
The accept function is called in a loop to keep accepting connections from multiple clients.
Run it from the terminal.
$ python server.py Socket created Socket bind complete Socket now listening
The output says that the socket was created, binded and then put into listening mode. At this point try to connect to this server from another terminal using the telnet command.
$ telnet localhost 8888
The telnet command should connect to the server right away and the server terminal would show this.
$ python server.py Socket created Socket bind complete Socket now listening Connected with 127.0.0.1:47758
So now our socket client (telnet) is connected to the socket server program.
Telnet (socket client) =========> Socket server
Handle socket clients with threads
The socket server shown above does not do much apart from accepting an incoming connection. Now its time to add some functionality to the socket server so that it can interact with the connected clients.
''' Simple socket server using threads ''' 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 as msg: print 'Bind failed. Error Code : ' + str(msg) + ' Message ' + msg 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 + ':' + str(addr) #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 the above server program and connect once again with a telnet from another terminal. This time if you type some message, the socket server will send it back with OK prefixed.
$ telnet localhost 8888 Trying 127.0.0.1... Connected to localhost. Escape character is '^]'. Welcome to the server. Type something and hit enter hello OK...hello how are you OK...how are you
The socket server can handle multiple clients simultaneously by allotting a separate thread to each.
Handle socket clients with select function
Threads appear the most natural way of handling multiple socket connections and clients. However there are other techniques of doing this. Polling is one such technique. In polling, the socket api will continuously check a bunch of sockets for some activity or event. And if an event occurs in one or multiple sockets, the function returns to the application the list of sockets on which the events occurred.
Such a kind of polling is achieved with the select function. The syntax of the select function is as follows
read_sockets,write_sockets,error_sockets = select(read_fds , write_fds, except_fds [, timeout]);
The select function takes 3 different sets/arrays of sockets. If any of the socket in the first set is readable or any socket in the second set is writable, or any socket in the third set has an error, then the function returns all those sockets. Next the application can handle the sockets returned and do the necessary tasks.
# Socket server in python using select function import socket, select if __name__ == "__main__": CONNECTION_LIST =  # list of socket clients 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 #Some incoming message from a client 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) # echo back the client message if data: sock.send('OK ... ' + data) # client disconnected, so remove from socket list 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()
The select function is given the list of connected sockets CONNECTION_LIST. The 2nd and 3rd parameters are kept empty since we do not need to check any sockets to be writable or having errors.
$ python server.py Chat server started on port 5000 Client (127.0.0.1, 55221) connected