Thursday, January 3, 2008

Writing a server with Python's asyncore module

The Python asyncore and aynchat modules

The Python standard library provides two modules---asyncore and
asynchat---to help in writing concurrent network servers using
event-based designs. The documentation does not give good examples,
so I am making some notes.

Overview

The basic idea behind the asyncore module is that:

  • there is a function, asyncore.loop() that does select() on a bunch of 'channels'. Channels are thin wrappers around sockets.
  • when select reports an event on any socket, loop() examines the event and the socket's state to create a higher level event;
  • it then calls a method on the channel corresponding to the higher level event.
asyncore provides a low-level, but flexible API to build network
servers. asynchat builds upon asyncore and provides an API that is
more suitable for request/response type of protocols.

aysncore

The asyncore module's API consists of:

  • the loop method, to be called by a driver program;
  • the dispatcher class, to be subclassed to do useful stuff. The dispatcher class is what is called 'channel' elsewhere.


+-------------+ +--------+
| driver code |---------> | loop() |
+-------------+ +--------+
| |
| | loop-dispatcher API (a)
| |
| +--------------+
| | dispatcher |
+----------------->| subclass |
+--------------+
|
| dispatcher-logic API (b)
|
+--------------+
| server logic |
+--------------+


This is all packaged nicely in an object oriented way. So, we have
the dispatcher class, that extends/wraps around the socket class (from
the socket module in the Python standard library). It provides all
the socket class' methods, as well as methods to handle the higher
level events. You are supposed to subclass dispatcher and implement
the event handling methods to do something useful.


The loop-dispatcher API

The loop function looks like this:

loop( [timeout[, use_poll[, map[,count]]]])

What is the map? It is a dictionary whose keys are the
file-descriptors, or fds, of the socket (i.e., socket.fileno()), and
whose values are the dispatcher objects.

When we create a dispatcher object, it automatically gets added to a
global list of sockets. The loop() function does a select() on this
list unless we provide an explicit map. (Hmm... we might always want
to use explicit maps; then our loop calls will be thread safe and we
will be able to launch multiple threads, each calling loop on
different maps.)

Methods a dispatcher subclass should implement

loop() needs some methods from the dispatcher object:

  • readable(): should return True, if you want the fd to be observed for read events;
  • writable(): should return True, if you want the fd to be observed for write events;

If either read or write is true, the corresponding fd will be examined
for errors also. Obviously, it makes no sense to have a dispatcher
which returns False for both readable() and writable().

  • handle_read: socket is readable; dispatcher.recv() can be used to actually get the data
  • handle_write: socket is writable; dispatcher.send(data) can be used to actually send the data
  • handle_error: socket encountered an error
  • handle_expt: socket received OOB data (not really used in practice)
  • handle_close: socket was closed remotely or locally
Server sockets get one more event.

  • handle_accept: a new incoming connection can be accept()ed. Call the accept() method really accept the connection. To create a server socket, call the bind() and listen() methods on it first.
Client sockets get this event:

  • handle_connect: connection to remote endpoint has been made. To initiate the connection, first call the connect() method on it.

Other socket methods are available in dispatch: create_socket(),
close(), set_resue_addr().


How to write a server using asyncore

The standard library documentation gives a client example, but not a
server example. Here are some notes on the latter.

  1. Subclass dispatched to create a listening socket
  2. In its handle_accept method, create new dispatchers. They'll get added to the global socket map.

Note: the handlers must not block or take too much time... or the
server won't be concurrent.

These socket-like functions that dispatcher extends should not be bypassed. They do funky things to detect higher level events. For e.g., how does asyncore figure out that the socket is closed? If I remember correctly, there are two ways to detect whether a non-blocking socket is closed:

  • select() returns a read event, but when you call recv()/read() you get zero bytes;
  • you call send()/write() and it fails with an error (sending zero bytes is not an error).

(I wish I had a copy of Unix Network Programming by Stevens handy
right now.)

Will look at asynchat in another post.

The code for the server is below:


asyncore_echo_server.py

import logging
import asyncore
import socket

logging.basicConfig(level=logging.DEBUG, format="%(created)-15s %(msecs)d %(levelname)8s %(thread)d %(name)s %(message)s")
log = logging.getLogger(__name__)

BACKLOG = 5
SIZE = 1024

class EchoHandler(asyncore.dispatcher):


def __init__(self, conn_sock, client_address, server):
self.server = server
self.client_address = client_address
self.buffer = ""

# We dont have anything to write, to start with
self.is_writable = False

# Create ourselves, but with an already provided socket
asyncore.dispatcher.__init__(self, conn_sock)
log.debug("created handler; waiting for loop")

def readable(self):
return True # We are always happy to read


def writable(self):
return self.is_writable # But we might not have
# anything to send all the time


def handle_read(self):
log.debug("handle_read")
data = self.recv(SIZE)
log.debug("after recv")
if data:
log.debug("got data")
self.buffer += data
self.is_writable = True # sth to send back now
else:
log.debug("got null data")

def handle_write(self):
log.debug("handle_write")
if self.buffer:
sent = self.send(self.buffer)
log.debug("sent data")
self.buffer = self.buffer[sent:]
else:
log.debug("nothing to send")
if len(self.buffer) == 0:
self.is_writable = False


# Will this ever get called? Does loop() call
# handle_close() if we called close, to start with?
def handle_close(self):
log.debug("handle_close")
log.info("conn_closed: client_address=%s:%s" % \
(self.client_address[0],
self.client_address[1]))
self.close()
#pass


class EchoServer(asyncore.dispatcher):

allow_reuse_address = False
request_queue_size = 5
address_family = socket.AF_INET
socket_type = socket.SOCK_STREAM


def __init__(self, address, handlerClass=EchoHandler):
self.address = address
self.handlerClass = handlerClass

asyncore.dispatcher.__init__(self)
self.create_socket(self.address_family,
self.socket_type)

if self.allow_reuse_address:
self.set_resue_addr()

self.server_bind()
self.server_activate()


def server_bind(self):
self.bind(self.address)
log.debug("bind: address=%s:%s" % (self.address[0], self.address[1]))


def server_activate(self):
self.listen(self.request_queue_size)
log.debug("listen: backlog=%d" % self.request_queue_size)


def fileno(self):
return self.socket.fileno()


def serve_forever(self):
asyncore.loop()


# TODO: try to implement handle_request()

# Internal use
def handle_accept(self):
(conn_sock, client_address) = self.accept()
if self.verify_request(conn_sock, client_address):
self.process_request(conn_sock, client_address)


def verify_request(self, conn_sock, client_address):
return True


def process_request(self, conn_sock, client_address):
log.info("conn_made: client_address=%s:%s" % \
(client_address[0],
client_address[1]))
self.handlerClass(conn_sock, client_address, self)


def handle_close(self):
self.close()


and to use it:

server = asyncore_echo_server.EchoServer((interface, port))
server.serve_forever()

17 comments:

Diodotus Reserva said...

Thank you.

xtealc said...

Thanks a lot.

sQuo said...

thanks a lot. This is an article I`ve been looking for ages ;)

Peter Wilkinson said...

Nice post.

There is one typo I've noticed, self.set_resue_addr() should be self.set_reuse_addr()

Edmond Dantes said...

great post, thanx

Neil Mclean said...

Many thanks for the code.

thugliffe said...

Thanks a lot.
probably just the thing i was searching for

Unknown said...

Thanks, man!

Now why couldn't someone have put something like that in the help file? Sheesh. Usually the docs are great, but I couldn't figure out how to use that module for the life of me!

Anonymous said...

Yiwu City is famous in the world for its biggest commodity markets and tremendous goods. 200, 000 foreign traders and wholesalers come to Yiwu market for purchasing varieties of China wholesale products. Nearly all leading China manufacturers and factories in Mainland China have showrooms in Yiwu wholesale market. All the prices in Yiwu market is the factory price. Because of good quality and LOW price, Yiwu commodities are exported to over 200 countries and regions. AmandaIEC is your ideal China sourcing partner and professional Yiwu Agent. You one stop sourcing agent, export agent, trade agent, purchasing agent, shipping agent in Yiwu China. promotional gifts, dollar items, lingerie wholesale, shoes wholesale, keychains wholesale and more. Wholesale shoes, Wholesale boots, Wholesale sneakers, Wholesale casual shoes, Wholesale dress shoes, Wholesale sandals, Wholesale slippers, from Yiwu, good quality, for all your cheap wholesale needs, your one stop China suppliers. Start your one stop China discount wholesale sourcing here today and experience top notch service and fast shipping. Welcome to Yiwu China to buy China wholesale products all at lowest wholesale price on www.AmandaIEC.com.

Affordable Luxurious Wedding Dress Blog said...

cheap wedding gownsdiscount bridal gownsChina wedding dressesChina wedding online store

Anonymous said...

Have you heared about 9Dragons which you need use Anarchy credits to play, and you can also borrow Anarchy Online credits from other players? But you can buy AO credits, or you will lose the choice if you do not have Anarchy online gold. If you get Anarchy gold, you can continue this game.
Have you heared about 9Dragons which you need use 9Dragons gold to play, and you can also borrow 9 Dragons gold from other players? But you can buy 9 Dragons gold, or you will lose the choice if you do not have cheap 9Dragons gold. If you get 9Dragons money, you can continue this game.

Anonymous said...

Hi, I like your blog, it's, you know, wonderful. I learn a lot from it.
Recently, I am always wanting to buy high quality, low price prince tennis rackets, and I find many places, where sells perfect tennis racket. In order to play the game and search the place where can buy the head tennis racket, and finally, I find thistennis shop. You can buy what you want in this store online.
Would you want to be more attractive? If your answer is "Yes", you can wear best-seller columbia jacket, you really need a pair of white ralph lauren jacket. They can make you become the attactive person.
Would you like wearing cheap polo shirt? Do you want to be a fashion chaser? Follow me and you can chase the most popular white polo shirts. it can help you increase your attraction, you will be the focus wherever you go only if you wear polo clothes.
polo shirt women
polo shirts men's
cheap polo jacket
high quality columbia jacket
claasic north face jacket
perfect spyder jacket
ralph Lauren shirts
polo A&F shirts
polo shirt
north face jackets
polo short sleeves shirt
ralph lauren polo long sleeves shirts
prince accessories
cheap head tennis racquets
wilson tennis racquets
superior babolat tennis racquets
head tennis racquet
babolat racquet
wilson tennis racquets

Anonymous said...

I like the side of the article, and very like your blog, to write well and hope to continue their efforts, we can see more of your articles. ed hardy clothes. After reading this article has strong feelings, the future will be Changlaikankan's.ed hardy swimwear. polo hoodies
ed hardy jeans
ed hardy
ed hardy clothing
ed hardy t-shirts
ed hardy clothes
ed-hardy.co.uk
ed hardy shirts
ed hardy mens
ed hardy clothes
ed hardy womens
ed hardy sunglasses
ed hardy swimwear
ed hardy Jeans
ed hardy hoodies
ed hardy bags
ed hardy trousers
ed hardy shoes
ed hardy sunglasses
ed hardy suits
ed
hardy
ed hardy dresses
ed hardy boots
ed hardy mens sweater
ed hardy womens cotton
ed hardy womens boots

Madiha Shoukat said...

It is a very informative and useful post thanks it is good material to read this post increases my knowledgetattoo sales

Madiha Shoukat said...

It definitely stretches the limits with the mind when you go through very good info and make an effort to interpret it properly. I am going to glance up this web site usually on my PCengraved watches

Busty London Escorts said...

Sexy London Girls - You will ask why our models is better then the other ones? Well, you have a unique possibility to ask them personally!

London Escorts

Unknown said...

This is very nice blog. I really appreciate the writing..
Regards
Hyderabad escorts,
Hyderabad escort services,
escort in Hyderabad