socket programming strategy

Andrew Bennetts andrew-pythonlist at puzzling.org
Sat Jan 18 20:39:59 EST 2003


On Sat, Jan 18, 2003 at 11:47:28PM +0530, Jeethu Rao wrote:
> I've been writing a multi threaded server,
> Can't use select, because there'll be a bit number 
> crunching to do.
> 
> My first stab at it was to use non blocking sockets
> And checking for the WSAEWOULDBLOCK in the exception
> Generated by the socket module. Obviously, it's Win32
> Only and very un portable. So, I started using 
> Timothy O'Malley's timeoutsocket module. But, I'm
> Not quite satisfied (and dissatisfaction is quite a
> Pain in the mind). Today, I hit upon the idea of using
> select() calls inside threads, waiting for just one socket.
> And I believe select() is a lot more efficient than simple
> Busy waiting loops. For Unix, I assume errno.EWOULDBLOCK is
> Quite the same as WSAEWOULDBLOCK.
> 
> Is this plan workable? Or have I missed something?

That sounds workable, I think, but very messy.

If you can chunk your number crunching into smallish pieces, then I'd
recommend using Twisted, and letting it take care of the intracacies of
non-blocking sockets for you.

An example of doing this in Twisted is:

--- longcalc.py ---
from twisted.protocols import basic
from twisted.internet import reactor
from twisted.internet.protocol import ServerFactory

class LongMultiplicationProtocol(basic.LineReceiver):
    """A protocol for doing long multiplications.

    It receives a list of numbers (seperated by whitespace) on a line, and
    writes back the answer.  The answer is calculated in chunks, so no one
    calculation should block for long enough to matter.
    """
    def connectionMade(self):
        self.workQueue = []
        
    def lineReceived(self, line):
        try:
            numbers = map(long, line.split())
        except ValueError:
            self.sendLine('Error.')
            return

        if len(numbers) <= 1:
            self.sendLine('Error.')
            return

        self.workQueue.append(numbers)
        reactor.callLater(0, self.calcChunk)

    def calcChunk(self):
        # Get the first bit of work off the queue
        work = self.workQueue[0]

        # Do a chunk of work: [a, b, c, ...] -> [a*b, c, ...]
        work[:2] = [work[0] * work[1]]

        # If this piece of work now has only one element, send it.
        if len(work) == 1:
            self.sendLine(str(work[0]))
            self.workQueue.pop()
        
        # Schedule this function to do more work, if there's still work to be
        # done.
        if self.workQueue:
            reactor.callLater(0, self.calcChunk)


class LongMultiplicationFactory(ServerFactory):
    protocol = LongMultiplicationProtocol


if __name__ == '__main__':
    from twisted.python import log
    import sys
    log.startLogging(sys.stdout)
    reactor.listenTCP(1234, LongMultiplicationFactory())
    reactor.run()
--- end longcalc.py---

That's less than sixty lines (with some comments and error-handling!), and
it is a complete implementation of a single-threaded non-blocking server
that accepts and processes many simultaneous connections, without blocking
for too long on any of them, by doing the work in pieces.

I've tested this on linux, but Twisted can do this just as well on win32.

-Andrew.






More information about the Python-list mailing list