Note
The material in this section realates to chapter 22 from The Text Book.
As they say on Monty Python’s Flying Circus show:
And now, for something completely different ...
Asynchronous servers use a totally different approach than multi-threaded servers. Special operating system system calls are used to allow sockets to send and receive data without blocking.
Note
socket.send() and socket.recv() are normally blocking system calls. That is to say, they don’t return until some potentially slow network activity completes. The delay with socket.recv() can be especially long as it waits for the client to send a message.
Note
The select.poll.poll() and select.select() functions are blocking functions. However, this is acceptable because they can monitor all of the sockets being used together. When the server is not processing a client request, we want the server to be blocked so that it does not unnecessarily use the computer’s CPU resources.
Put a socket into non-blocking mode. socket.setblocking(1) puts the socket back into blocking mode, which is the initial mode for new sockets.
Usage of select.select() and select.poll is almost the same
They both are given a list of sockets to watch for activity (Unix allows file descriptors also):
- Sockets having received data
- Sockets ready to send data
- Sockets in an error state
A Detailed select.poll example may be found in The Text Book. However, since most students enrolled in this class use Windows, rather than Unix, I’ll leave the discussion of select.poll to the book and I’ll focus on select.select().
Wait until one or more socket (or file descriptor, in Unix) is ready for some kind of I/O. The first three arguments passed to the constructor are lists of sockets to be waited for.
| Parameters: |
|
|---|---|
| Return type: | tuple of three lists, which are a subset of the sockets in rlist, wlist and xlist. |
Returns a polling object, which supports registering and unregistering file descriptors, and then polling them for I/O events
Register a file descriptor with the polling object. Future calls to the poll.poll() method will then check whether the file descriptor has any pending I/O events.
Polls the set of registered file descriptors, and returns a possibly-empty list containing (fd, event) 2-tuples for the descriptors that have events or errors to report.
Note that, as a practical matter, the reading list of sockets is often the only one used. If we want to listen to a socket for incoming connections and a set of sockets for data, we might use the following minimal model as a starting point for developing an asynchronous server. The handle_request() in this example is a function (not defined here), which would determine what the client needs; take the appropriate actions; and, most likely, send a message back to the client.
import select
import socket
port = 5000
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind(('',port))
server.listen(1)
server.setblocking(0)
rlist = [server]
clients = {}
while True:
try:
in_list, out, excpt = select.select(rlist, [], [])
except select.error, e:
break
except socket.error, e:
break
for sock in in_list:
if sock == server:
client_sock, address = self.server.accept()
client_sock.setblocking(0)
rlist.append(client_sock)
clients[client_sock] = address
else:
message = sock.recv()
if len(message):
handle_request(message, sock, clients[sock])
else:
del clients[sock]
rlist.remove(sock)
sock.close()
#--
server.close()
Here is a detailed select.select() example: chatSelectServer.py
- author: Anand Balachandran Pillai [PILLAI07]
- URL: ActiveState Code
When select.select() returns with a socket in the receive list, then a socket.recv() call will return data immediately.
The ready-to-send socket list is often omitted or ignored. A socket in this list just means that the socket is in a state where it can send data immediately.
The select.poll example in the book (echoserve.py) uses socket bit-masks and the select.poll.register() function to force a socket into the ready-to-send list as soon as it’s available. That functionality is not available with select.select().
Here is an example Twisted program taken from The Text Book. This example fairly well illustrates the above points:
#!/usr/bin/env python
# Asynchronous Chat Server with Twisted - Chapter 22
# twistedchatserver.py
# Twisted 1.1.1 or above required for this example
# -- download from www.twistedmatrix.com
from twisted.internet.protocol import Factory
from twisted.protocols.basic import LineOnlyReceiver
from twisted.internet import reactor
class Chat(LineOnlyReceiver):
def lineReceived(self, data):
self.factory.sendAll("%s: %s" % (self.getId(), data))
def getId(self):
return str(self.transport.getPeer())
def connectionMade(self):
print "New connection from", self.getId()
self.transport.write("Welcome to the chat server, %s\n" %\
self.getId())
self.factory.addClient(self)
def connectionLost(self, reason):
self.factory.delClient(self)
class ChatFactory(Factory):
protocol = Chat
def __init__(self):
self.clients = []
def addClient(self, newclient):
self.clients.append(newclient)
def delClient(self, client):
self.clients.remove(client)
def sendAll(self, message):
for proto in self.clients:
proto.transport.write(message + "\n")
reactor.listenTCP(51423, ChatFactory())
reactor.run()