Asynchronous IO with select and poll

When a server communicates with a client, the data it receives from the client may come in fits and spurts. If you're using forking and threading, that's not a problem. While one parallel waits for data, other parallels may continue dealing with their own clients. Another way to go, however, is to deal only with the clients that actually have something to say at a given moment. You don't even have to hear them out—you just hear (or, rather, read) a little, and then put it back in line with the others.

This is the approach taken by the frameworks asyncore/asynchat (see Chapter 24) and Twisted (see the following section). The basis for this kind of functionality is the select function, or, where available, the poll function, both from the select module. Of the two, poll is more scalable, but it is available only in UNIX systems (that is, not in Windows).

The select function takes three sequences as its mandatory arguments, with an optional timeout in seconds as its fourth argument. The sequences are file descriptor integers (or objects with a fileno method that return such an integer). These are the connections that we're waiting for. The three sequences are for input, output, and exceptional conditions (errors and the like). If no timeout is given, select blocks (that is, waits) until one of the file descriptors is ready for action. If a timeout is given, select blocks for at most that many seconds, with zero giving a straight poll (that is, no blocking). select returns three sequences (a triple—that is, a tuple of length three), each representing an active subset of the corresponding parameter. For example, the first sequence returned will be a sequence of input file descriptors where there is something to read.

The sequences can, for example, contain file objects (not in Windows) or sockets. Listing 14-6 shows a server using select to serve several connections. (Note that the server socket itself is supplied to select, so that it can signal when there are new connections ready to be accepted.) The server is a simple logger that prints out (locally) all data received from its clients. You can test it by connecting to it using telnet (or by writing a simple socket-based client that feeds it some data). Try connecting with multiple telnet connections to see that it can serve more than one client at once (although its log will then be a mixture of the input from the two).

Listing 14-6. A Simple Server Using select import socket, select s = socket.socket()

host = socket.gethostname() port = 1234 s.bind((host, port))

rs, ws, es = select.select(inputs, [], []) for r in rs: if r is s:

print 'Got connection from', addr inputs.append(c)

else: try:

data = r.recv(1024) disconnected = not data except socket.error: disconnected = True if disconnected:

print r.getpeername(), 'disconnected' inputs.remove(r) else:

print data

The poll method is easier to use than select. When you call poll, you get a poll object. You can then register file descriptors (or objects with a fileno method) with the poll object, using its register method. You can later remove such objects again, using the unregister method. Once you've registered some objects (for example, sockets), you can call the poll method (with an optional timeout argument) and get a list (possibly empty) of pairs of the form (fd, event), where fd is the file descriptor and event tells you what happened. It's a bitmask, meaning that it's an integer where the individual bits correspond to various events. The various events are constants of the select module, and are explained in Table 14-2. To check whether a given bit is set (that is, if a given event occurred), you use the bitwise and operator (&), like this:

Table 14-2. Polling Event Constants in the select Module

Event Name

Description

POLLIN

There is data to read available from the file descriptor.

POLLPRI

There is urgent data to read from the file descriptor.

POLLOUT

The file descriptor is ready for data, and will not block if written to.

POLLERR

Some error condition is associated with the file descriptor.

POLLHUP

Hung up. The connection has been lost.

POLLNVAL

Invalid request. The connection is not open.

The program in Listing 14-7 is a rewrite of the server from Listing 14-6, now using poll instead of select. Note that I've added a map (fdmap) from file descriptors (ints) to socket objects.

Listing 14-7. A Simple Server Using poll import socket, select s = socket.socket()

host = socket.gethostname() port = 1234 s.bind((host, port))

s.listen(5) p = select.poll() p.register(s) while True:

events = p.poll() for fd, event in events: if fd in fdmap:

c, addr = s.accept() print 'Got connection from', addr p.register(c) fdmap[c.fileno()] = c elif event & select.POLLIN: data = fdmap[fd].recv(1024) if not data: # No data -- connection closed print fdmap[fd].getpeername(), 'disconnected' p.unregister(fd) del fdmap[fd] else:

print data

You can find more information about select and poll in the Python Library Reference (http://python.org/doc/lib/module-select.html). Also, reading the source code of the standard library modules asyncore and asynchat (found in the asyncore.py and asynchat.py files in your Python installation) can be enlightening.

0 0

Post a comment

  • Receive news updates via email from this site