Multiplexing a web server using select.epoll

Python's select module has a few platform-specific, networking event management functions. On a Linux machine, epoll is available. This will utilize the operating system kernel that will poll network events and let our script know whenever something happens. This sounds more efficient than the previously mentioned select.select approach.

How to do it...

Let's write a simple web server that can return a single line of text to any connected web browser.

The core idea is during the initialization of this web server, we should make a call to select.epoll() and register our server's file descriptor for event notifications. In the web server's executive code, the socket event is monitored as follows:

Listing 2.4 Simple web server using select.epoll
#!/usr/bin/env python
# Python Network Programming Cookbook -- Chapter - 2
# This program is optimized for Python 2.7
# It may run on any other version with/without modifications.
import socket
import select
import argparse
SERVER_HOST = 'localhost'
EOL1 = b'

'
EOL2 = b'

'
SERVER_RESPONSE  = b"""HTTP/1.1 200 OK
Date: Mon, 1 Apr 2013 01:01:01 GMT
Content-Type: text/plain
Content-Length: 25


Hello from Epoll Server!"""

class EpollServer(object):
    """ A socket server using Epoll"""
    def __init__(self, host=SERVER_HOST, port=0):
      self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
      self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
      self.sock.bind((host, port))
      self.sock.listen(1)
      self.sock.setblocking(0)
      self.sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
      print "Started Epoll Server"
      self.epoll = select.epoll()
      self.epoll.register(self.sock.fileno(), select.EPOLLIN)
    
 def run(self):
  """Executes epoll server operation"""
  try:
     connections = {}; requests = {}; responses = {}
     while True:
   events = self.epoll.poll(1)
   for fileno, event in events:
     if fileno == self.sock.fileno():
       connection, address = self.sock.accept()
       connection.setblocking(0)
       self.epoll.register(connection.fileno(), select.EPOLLIN)
       connections[connection.fileno()] = connection
       requests[connection.fileno()] = b''
       responses[connection.fileno()] = SERVER_RESPONSE
     elif event & select.EPOLLIN:
       requests[fileno] += connections[fileno].recv(1024)
       if EOL1 in requests[fileno] or EOL2 in requests[fileno]:
             self.epoll.modify(fileno, select.EPOLLOUT)
             print('-'*40 + '
' + requests[fileno].decode()[:-2])
      elif event & select.EPOLLOUT:
         byteswritten = connections[fileno].send(responses[fileno])
         responses[fileno] = responses[fileno][byteswritten:]
         if len(responses[fileno]) == 0:
             self.epoll.modify(fileno, 0)
             connections[fileno].shutdown(socket.SHUT_RDWR)
         elif event & select.EPOLLHUP:
              self.epoll.unregister(fileno)
              connections[fileno].close()
              del connections[fileno]
 finally:
   self.epoll.unregister(self.sock.fileno())
   self.epoll.close()
   self.sock.close()

if __name__ == '__main__':
 parser = argparse.ArgumentParser(description='Socket Server Example with Epoll')
 parser.add_argument('--port', action="store", dest="port", type=int, required=True)
    given_args = parser.parse_args()
    port = given_args.port
    server = EpollServer(host=SERVER_HOST, port=port)
    server.run()

If you run this script and access the web server from your browser, such as Firefox or IE, by entering http://localhost:8800/, the following output will be shown in the console:

$ python 2_4_simple_web_server_with_epoll.py --port=8800
Started Epoll Server
----------------------------------------
GET / HTTP/1.1
Host: localhost:8800
Connection: keep-alive
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 Safari/537.31
DNT: 1
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3
Cookie: MoodleSession=69149dqnvhett7br3qebsrcmh1; MOODLEID1_=%257F%25BA%2B%2540V

----------------------------------------
GET /favicon.ico HTTP/1.1
Host: localhost:8800
Connection: keep-alive
Accept: */*
DNT: 1
User-Agent: Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.43 Safari/537.31
Accept-Encoding: gzip,deflate,sdch
Accept-Language: en-GB,en-US;q=0.8,en;q=0.6
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.3

You will also be able to see the following line in your browser:

Hello from Epoll Server!

The following screenshot shows the scenario:

How to do it...

How it works...

In our EpollServer web server's constructor, a socket server is created and bound to a localhost at a given port. The server's socket is set to the non-blocking mode (setblocking(0)). The TCP_NODELAY option is also set so that our server can exchange data without buffering (as in the case of an SSH connection). Next, the select.epoll() instance is created and the socket's file descriptor is passed to that instance to help monitoring.

In the run() method of the web server, it starts receiving the socket events. These events are denoted as follows:

  • EPOLLIN: This socket reads events
  • EPOLLOUT: This socket writes events

In case of a server socket, it sets up the response SERVER_RESPONSE. When the socket has any connection that wants to write data, it can do that inside the EPOLLOUT event case. The EPOLLHUP event signals an unexpected close to a socket that is due to the internal error conditions.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.46.69