Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

The joys and jilts of non-blocking sockets

11 views
Skip to first unread message

Robert Amesz

unread,
May 5, 2001, 9:46:16 AM5/5/01
to
I've recently been doing a little work with sockets, more in particular
non-blocking sockets, and I'm sorry to say the standard Python
documentation isn't really too helpful here. I feel this is a mistake:
without documentation people like me will experiment to find out how
things work, and we may end up relying on features which are either
different for different platforms, or not guaranteed to work with
different versions of Python, or both. This is not good. I've
documented my experiments in the hope that will be useful to others and
also to elicit some comments, in particular where other platforms or
versions of Python are concerned.

I've also studied timeoutsocket.py for some hints and pointers about
socket behaviour, and this is a good source of information about some
of the quirks of non-blocking sockets, so I'd like to thank
Timothy O'Malley for that.

Even so I'd like to take the opportunity to point out a few bugs and a
design flaw in version 1.15 (the latest version I was able to find).
One of the bugs is/are a set of missing commas in lines 142, 143, 144
and 147: without those commas tuples aren't tuples, I'm afraid. (My
guess is those were lists originally.)

The other, slightly larger bug is that error code 10022 (in
TimeoutSocket.connect()) is taken as an indication that the connection
has been made, while in fact the connection has been refused (see below
for more details about that).

The design flaw is that the module makes non-blocking sockets behave
like blocking ones: this just doesn't make sense to me. Arguably, using
both types of sockets in a single application shouldn't be too common,
but as it - very cleverly - replaces the normal socket-module once
imported, it really should handle the non-blocking case too. Not that
it's hard: in fact, it's almost trivial, but it should be done.

But let's concentrate on socket behaviour itself. The observations
below have been done on an Windows 98 machine, they might be different
on other Windows versions, and they certainly *will* de different on a
different OS, like UNIX or MAC-OS. I'm using Python version:

Python 2.1 (#15, Apr 16 2001, 18:25:49) [MSC 32 bit (Intel)] on win32

Exceptions are shown on an indented line as they are displayed by
'print' or the stack trace, and on the next line you'll find the
symbolic name(s) for that code, as defined in module 'errno'. The codes
starting with 'WSA' are from the Windows sockets .dll.

CONNECTING THE SOCKET
---------------------

If the host exists and can be reached, connecting to it using a non-
blocking call *always* leads to this exception:

(10035, 'The socket operation could not complete without blocking')
10035 = EWOULDBLOCK WSAEWOULDBLOCK

This message does not give you *any* information about the status of
the connection: the machine may be busy connecting, the connection may
have been made, or the connection may have been refused.

If the connection is refused (i.e. there's either no service listening
to the port you're trying to connect to, or no new connections are
being accepted on that port), trying to receive (or send) data through
that socket will, once again, produce the same exception (10035), so
that won't really help you to find out what your connection status is.
Using getpeername() is more helpful:

(10057, 'Socket is not connected')
10057 = ENOTCONN WSAENOTCONN

Unfortunately, the manual says this function doesn't exist on all
platforms, so portable code should try to avoid it.

But don't despair: if a connection is refused trying to connect again
to the same host using the same socket will yield the following
surprising exception:

(10022, 'Invalid argument')
10022 = WSAEINVAL

Well, it accepted the parameter(s) before, so what's that all about?
Furthermore, it doesn't make any difference if you change the port
number, you'll get the same result. Using a different hostname which
points to the same IP-address doesn't change anything either, but using
a different hostname *does*, strangely enough.

As sockets can't be re-used anyway this isn't something to look into
too deeply. After a close() all further operations on that socket are
are expressly forbidden, and in fact impossible. Yes, I just had to
try! Although the exception you get when you try to reconnect is pretty
puzzling:

AttributeError: 'int' object has no attribute 'connect'

What happens here is that the internal socket object has been replaced
by the int 0. But please don't rely on behaviour like that.


Ok, back to connecting. Because I did my testing on a single machine I
wasn't able to catch the socket system in the middle of the connection
handshake, so I can't tell if trying to connect at that time will yield
another exception, but trying it after establishing the connection will
result in this very predictable exception.

(10056, 'Socket is already connected')
10056 = EISCONN WSAEISCONN


Hurrah, we're connected! Or are we? Well, not neccesarily: the
connection may have been broken already. The system doesn't seem to be
able to tell an idle connection from a broken one (this may be part of
the nature of TCP/IP), and you need to do something with the stream to
find that out, as you'll see below.

On the other hand, if you try to connect to a non-existent IP-address
you'll see this exception:

(10065, 'No route to host')
10065 = EHOSTUNREACH WSAEHOSTUNREACH


If the hostname couldn't be resolved, this is what you get:

('host not found',)

What, no error code? That's right, and this could be an issue if you
expect a number in the first position of this tuple-like exception, or
anything at all in the second position.

SENDING DATA
------------

Pretty straightforward, really. Just do MySocket.send(data), and if
there's nothing wrong with the connection it will either work, or
raise:

(10035, 'The socket operation could not complete without blocking')
10035 = EWOULDBLOCK WSAEWOULDBLOCK

In the documentation it states that the function returns the number of
bytes actually sent, but I've never observed this number to be
different from the amount you're trying to send, even when it's a big
chunk of data. When trying to flood a connection with data with small
bits of data (I didn't read the data on the receiving end) it would
raise the above exception after about 18K of data was 'sent', but if
you try to send() larger (even much larger) chunks of data the first
call always works, and only subsequent calls raise the exception. This
behaviour might not be portable, though.

If you try to send data when the connection has been broken the
following exception is raised:

(10054, 'Connection reset by peer')
10054 = ECONNRESET WSAECONNRESET

RECEIVING DATA
--------------

Doing a MySocket.recv(max_length) can certainly result in some
unexpected behaviour: if the connection is good, and there's some data
waiting, the data will be returned. That's not the surprising bit. When
the connection is good, and there's no data waiting, you'll get the
ubiquitous

(10035, 'The socket operation could not complete without blocking')
10035 = EWOULDBLOCK WSAEWOULDBLOCK

exception. That, too, isn't surprising. What *is* surprising, however, is
that when the connection has been broken on the other end, no exception
is raised whatsoever, but the recv() function will keep returning zero-
length strings. I wonder if that behaviour is intentional? Or portable,
for that matter. As this is the only way that I know of telling a dead
connection from a live one when receiving data, we're forced to rely on
this strange behaviour, but I'd prefer the ECONNRESET-exception to would
be raised.

SOCKET EXCEPTIONS
-----------------

Sockets raise exceptions of type socket.error, and like any other
exception that's a class. But you might be forgiven for thinking that
it's a tuple because for all intents and purposes it behaves like one.
(I presume this is for historic reasons, to make sure older code will
keep working as expected.) It looks that way in the traceback, and if e
is the exception you've caught you can look at e[0] (the number of the
error) and e[1] (the associated message). This rule has one exception,
however, and that is the ('host not found',) exception, which has the
error message in the first position, and doesn't have a second position.


Strange beasts, those sockets. Under Windows, anyway.


Robert Amesz
--

APPENDIX - socket error codes from the 'errno' module

10004 = WSAEINTR
10009 = WSAEBADF
10013 = WSAEACCES
10014 = WSAEFAULT
10022 = WSAEINVAL
10024 = WSAEMFILE
10035 = EWOULDBLOCK WSAEWOULDBLOCK
10036 = EINPROGRESS WSAEINPROGRESS
10037 = EALREADY WSAEALREADY
10038 = ENOTSOCK WSAENOTSOCK
10039 = EDESTADDRREQ WSAEDESTADDRREQ
10040 = EMSGSIZE WSAEMSGSIZE
10041 = EPROTOTYPE WSAEPROTOTYPE
10042 = ENOPROTOOPT WSAENOPROTOOPT
10043 = EPROTONOSUPPORT WSAEPROTONOSUPPORT
10044 = ESOCKTNOSUPPORT WSAESOCKTNOSUPPORT
10045 = EOPNOTSUPP WSAEOPNOTSUPP
10046 = EPFNOSUPPORT WSAEPFNOSUPPORT
10047 = EAFNOSUPPORT WSAEAFNOSUPPORT
10048 = EADDRINUSE WSAEADDRINUSE
10049 = EADDRNOTAVAIL WSAEADDRNOTAVAIL
10050 = ENETDOWN WSAENETDOWN
10051 = ENETUNREACH WSAENETUNREACH
10052 = ENETRESET WSAENETRESET
10053 = ECONNABORTED WSAECONNABORTED
10054 = ECONNRESET WSAECONNRESET
10055 = ENOBUFS WSAENOBUFS
10056 = EISCONN WSAEISCONN
10057 = ENOTCONN WSAENOTCONN
10058 = ESHUTDOWN WSAESHUTDOWN
10059 = ETOOMANYREFS WSAETOOMANYREFS
10060 = ETIMEDOUT WSAETIMEDOUT
10061 = ECONNREFUSED WSAECONNREFUSED
10062 = ELOOP WSAELOOP
10063 = WSAENAMETOOLONG
10064 = EHOSTDOWN WSAEHOSTDOWN
10065 = EHOSTUNREACH WSAEHOSTUNREACH
10066 = WSAENOTEMPTY
10067 = WSAEPROCLIM
10068 = EUSERS WSAEUSERS
10069 = EDQUOT WSAEDQUOT
10070 = ESTALE WSAESTALE
10071 = EREMOTE WSAEREMOTE
10091 = WSASYSNOTREADY
10092 = WSAVERNOTSUPPORTED
10093 = WSANOTINITIALISED
10101 = WSAEDISCON

bowman

unread,
May 5, 2001, 10:21:59 AM5/5/01
to

"Robert Amesz" <rcameszR...@dds.removethistoo.nl> wrote in message
news:Xns90989FD8...@127.0.0.1...

> I've recently been doing a little work with sockets, more in particular
> non-blocking sockets, and I'm sorry to say the standard Python
> documentation isn't really too helpful here.

You might want to start with reading one of W. Richard Stevens' books that
deal with socket programming. Win98 isn't the best implementation to do
socket programming on, either.

Steve Holden

unread,
May 5, 2001, 2:55:05 PM5/5/01
to
"Robert Amesz" <rcameszR...@dds.removethistoo.nl> wrote ...

> I've recently been doing a little work with sockets, more in particular
> non-blocking sockets, and I'm sorry to say the standard Python
> documentation isn't really too helpful here. I feel this is a mistake:
> without documentation people like me will experiment to find out how
> things work, and we may end up relying on features which are either
> different for different platforms, or not guaranteed to work with
> different versions of Python, or both. This is not good. I've
> documented my experiments in the hope that will be useful to others and
> also to elicit some comments, in particular where other platforms or
> versions of Python are concerned.
>
Non-blocking sockets certainly aren't easy. The best use I've seen is Sam
Rushing's (and now the standard library's) asyncore and astnchat modules.
They probably do what you need.

> I've also studied timeoutsocket.py for some hints and pointers about
> socket behaviour, and this is a good source of information about some
> of the quirks of non-blocking sockets, so I'd like to thank
> Timothy O'Malley for that.
>
> Even so I'd like to take the opportunity to point out a few bugs and a
> design flaw in version 1.15 (the latest version I was able to find).
> One of the bugs is/are a set of missing commas in lines 142, 143, 144
> and 147: without those commas tuples aren't tuples, I'm afraid. (My
> guess is those were lists originally.)
>
> The other, slightly larger bug is that error code 10022 (in
> TimeoutSocket.connect()) is taken as an indication that the connection
> has been made, while in fact the connection has been refused (see below
> for more details about that).
>

Tim doesn't have a Windows machine to test on, and so he relies on bug
reports from Windows users. Please let him have a copy of this mail if you
didn't already.

> The design flaw is that the module makes non-blocking sockets behave
> like blocking ones: this just doesn't make sense to me. Arguably, using
> both types of sockets in a single application shouldn't be too common,
> but as it - very cleverly - replaces the normal socket-module once
> imported, it really should handle the non-blocking case too. Not that
> it's hard: in fact, it's almost trivial, but it should be done.
>

[ ... ]

regards
Steve


Timothy O'Malley

unread,
May 9, 2001, 1:10:27 AM5/9/01
to
[[ This message was both posted and mailed: see
the "To," "Cc," and "Newsgroups" headers for details. ]]

hola.

In article <Xns90989FD8...@127.0.0.1>, Robert Amesz wrote:
> I've also studied timeoutsocket.py for some hints and pointers about
> socket behaviour, and this is a good source of information about some
> of the quirks of non-blocking sockets, so I'd like to thank
> Timothy O'Malley for that.

Thanks.

> Even so I'd like to take the opportunity to point out a few bugs and a
> design flaw in version 1.15 (the latest version I was able to find).
> One of the bugs is/are a set of missing commas in lines 142, 143, 144
> and 147: without those commas tuples aren't tuples, I'm afraid. (My
> guess is those were lists originally.)

Again, thanks. Those shortcuts were recently added to remove
the name lookups (and platform dependency) from the connect()
and accept() routines. Glad to hear of the problem and make the
fixes.

> The design flaw is that the module makes non-blocking sockets behave
> like blocking ones: this just doesn't make sense to me.

It's true. When I first wrote TimeoutSocket, I had it in my
mind that it would *only* be used with blocking sockets. (Why would
anyone need timeouts when none of their operations block?) This
assumption broke down when I added the silent shim effect, whereby
it surreptiously replaces the real socket module.

I have had it on my "back burner" projects to fix this. I haven't
got around to it -- largely because no one has noticed it before.
In general, people do not use this module with non-blocking sockets.

> but as it - very cleverly - replaces the normal socket-module once
> imported, it really should handle the non-blocking case too. Not that
> it's hard: in fact, it's almost trivial, but it should be done.

All true. In version 1.16, ok?

> If the host exists and can be reached, connecting to it using a non-
> blocking call *always* leads to this exception:
>
> (10035, 'The socket operation could not complete without blocking')
> 10035 = EWOULDBLOCK WSAEWOULDBLOCK
>
> This message does not give you *any* information about the status of
> the connection: the machine may be busy connecting, the connection may
> have been made, or the connection may have been refused.

You lose me here. By using a non-blocking socket, you have told
the operating system that it should never block on this socket.
In that case, I expect this exception. The operating system is
informing you that the operation would block (because it wasn't
finished) when you made the connect() call. This is all standard
and routine behavior for non-blocking sockets.

> If the connection is refused (i.e. there's either no service listening
> to the port you're trying to connect to, or no new connections are
> being accepted on that port), trying to receive (or send) data through
> that socket will, once again, produce the same exception (10035), so
> that won't really help you to find out what your connection status is.

You should use select() and wait for the socket to be writeable to
determine when a socket is connected. Again....standard procedure.


> But don't despair: if a connection is refused trying to connect again
> to the same host using the same socket will yield the following
> surprising exception:
>
> (10022, 'Invalid argument')
> 10022 = WSAEINVAL

In fact, once the socket is 'writeable' (using the above test), you
are supposed to call connect() again. The result of the second
connect() call determines the result of the overall connection
operation.

I admit, I do not know what this Windows error message means. Even
so, that does not mean that it is a failure of the interface.
Sometimes connections have errors -- this is all part of normal
operation.

> As sockets can't be re-used anyway this isn't something to look into
> too deeply. After a close() all further operations on that socket are
> are expressly forbidden, and in fact impossible. Yes, I just had to
> try! Although the exception you get when you try to reconnect is pretty
> puzzling:
>
> AttributeError: 'int' object has no attribute 'connect'

Ooops. That looks like a TimeoutSocket bug.


> Ok, back to connecting. Because I did my testing on a single machine I
> wasn't able to catch the socket system in the middle of the connection
> handshake, so I can't tell if trying to connect at that time will yield
> another exception, but trying it after establishing the connection will
> result in this very predictable exception.
>
> (10056, 'Socket is already connected')
> 10056 = EISCONN WSAEISCONN

As expected. This is the correct response.

> Hurrah, we're connected! Or are we? Well, not neccesarily: the
> connection may have been broken already. The system doesn't seem to be
> able to tell an idle connection from a broken one (this may be part of
> the nature of TCP/IP),

It is -- at least to a first approximation. Before worrying about the
limitations of TCP, it'd be good to have a strong understanding of the
"normal" case.

In summary of this long message.....
- Thanks for the bug report.
- You are right about the non-blocking inadequacies of TimeoutSocket.
- Much of what you have complained about is standard procedure
when using sockets. A whirlwind tour through BSD sockets would
explain some of the history.

good luck.

Robert Amesz

unread,
May 14, 2001, 1:24:15 PM5/14/01
to
Timothy O'Malley wrote:

> In article <Xns90989FD8...@127.0.0.1>, Robert Amesz wrote:
>

>> [non-blocking socket fix for TimeOutSocket.py]

>
> All true. In version 1.16, ok?

That's fine with me, thanks.


>> If the host exists and can be reached, connecting to it using a
>> non- blocking call *always* leads to this exception:
>>
>> (10035, 'The socket operation could not complete without
>> blocking') 10035 = EWOULDBLOCK WSAEWOULDBLOCK
>>
>> This message does not give you *any* information about the status
>> of the connection: the machine may be busy connecting, the
>> connection may have been made, or the connection may have been
>> refused.
>
> You lose me here. By using a non-blocking socket, you have told
> the operating system that it should never block on this socket.
> In that case, I expect this exception. The operating system is
> informing you that the operation would block (because it wasn't
> finished) when you made the connect() call. This is all standard
> and routine behavior for non-blocking sockets.

Perhaps I haven't made myself clear. The point I'm trying to make is
that I'm suprised that an exception is raised for a case which is not
exceptional at all. It's more of an aesthetic argument, but

try:
MySock.connect(('some.dot.com', 80)) # Nonblocking socket
# This point cannot be reached

except socket.error e:
if e[0] != EWOULDBLOCK:
raise

just seems ugly and obtuse. Oh, I can live with it, but Python doesn't
usually offend my sensibilities in this way.


>> But don't despair: if a connection is refused trying to connect
>> again to the same host using the same socket will yield the
>> following surprising exception:
>>
>> (10022, 'Invalid argument')
>> 10022 = WSAEINVAL
>
> In fact, once the socket is 'writeable' (using the above test), you
> are supposed to call connect() again. The result of the second
> connect() call determines the result of the overall connection
> operation.
>
> I admit, I do not know what this Windows error message means. Even
> so, that does not mean that it is a failure of the interface.
> Sometimes connections have errors -- this is all part of normal
> operation.

I've been looking through the Winsock API doc's and I've found the
reason:

WSAEALREADY
A non-blocking connect call is in progress on the specified
socket.
Note In order to preserve backward compatibility, this
error is reported as WSAEINVAL to Windows Sockets 1.1 applications
that link to either WINSOCK.DLL or WSOCK32.DLL.

So it would seem that this is an old Winsock-bug, preserved for
compatibility reasons. This could easily be fixed by either having
WSAStartup() request Winsock version 2.0, or checking the return code
from connect() for WSAEINVAL and replacing it with WSAEALREADY.


>> As sockets can't be re-used anyway this isn't something to look
>> into too deeply. After a close() all further operations on that
>> socket are are expressly forbidden, and in fact impossible. Yes, I
>> just had to try! Although the exception you get when you try to
>> reconnect is pretty puzzling:
>>
>> AttributeError: 'int' object has no attribute 'connect'
>
> Ooops. That looks like a TimeoutSocket bug.

Don't worry: it's the standard socket module which is to blame. It
apparrently replaces the internal _socket object with the integer 0
when closed.


> In summary of this long message.....
> - Thanks for the bug report.

You're very welcome.


> - You are right about the non-blocking inadequacies of
> TimeoutSocket.

Shouldn't be hard to fix, though.


> - Much of what you have complained about is
> standard procedure when using sockets.

I wasn't complaining, just describing my experiences. But I'm happy you
confirmed that many things *are* standard procedure, and not just
winsock things.


> A whirlwind tour through BSD sockets would explain some of the
> history.

Perhaps I should. The documentation that I have on Berkely sockets
isn't very extensive and doesn't address non-blocking sockets at all.
I'm sure the web will yield more comprehensive information, though, if
I care to look for it.

Besides, there are differences between sockets implementations on
different platforms, and the Python socket module doesn't do much, it
would seem, to isolate the Python programmer from those platform
dependencies. I'm not sure that is a good decision, but I'm afraid
we'll have to live with it.

Thank you for your comments.


Robert Amesz

Fredrik Lundh

unread,
May 14, 2001, 2:09:49 PM5/14/01
to
Robert Amesz wrote:
> Perhaps I haven't made myself clear. The point I'm trying to make is
> that I'm suprised that an exception is raised for a case which is not
> exceptional at all.

If you read up on socket behaviour, you'll notice that that error
means the connection couldn't be *immediately* established. If
that's an error or not depends on your application.

And if you look in the Python docs, you'll notice that you can use
"connect_ex" instead of "connect" if you don't want the exception.

Cheers /F


Timothy O'Malley

unread,
May 15, 2001, 1:14:25 AM5/15/01
to
hola.

In article <NbVL6.10438$sk3.2...@newsb.telia.net>, Fredrik Lundh wrote:
> And if you look in the Python docs, you'll notice that you can use
> "connect_ex" instead of "connect" if you don't want the exception.

Argh! Don't tell him that -- timeoutsocket.py doesn't properly
wrap that function.

Another thing on the ToDo list.

Robert Amesz

unread,
May 15, 2001, 8:42:28 PM5/15/01
to
Fredrik Lundh wrote:

> Robert Amesz wrote:
>> Perhaps I haven't made myself clear. The point I'm trying to make
>> is that I'm suprised that an exception is raised for a case which
>> is not exceptional at all.
>
> If you read up on socket behaviour, you'll notice that that error
> means the connection couldn't be *immediately* established. If
> that's an error or not depends on your application.

Hmmm, if connecting to 'localhost' isn't immediate, what is? Because
that's how I tested it. (Not that I'd rely on connect() *not*
connecting immediately, but on my Windows box it obviously doesn't.)


> And if you look in the Python docs, you'll notice that you can use
> "connect_ex" instead of "connect" if you don't want the exception.

/me looks in Python docs and gasps in suprise

Oh my, how could I have overlooked that? That's pretty useful, thanks.

/me studies timeoutsocket.py

Oh, dear, connect_ex() is nowhere to be found in there. Tim O'Malley
isn't going to like this.


Robert Amesz


Robert Amesz

unread,
May 30, 2001, 7:01:37 PM5/30/01
to
I wrote:

> [Non-blocking connect under Win98, Python 2.1]


>
> But don't despair: if a connection is refused trying to connect
> again to the same host using the same socket will yield the
> following surprising exception:
>
> (10022, 'Invalid argument')
> 10022 = WSAEINVAL
>
> Well, it accepted the parameter(s) before, so what's that all
> about? Furthermore, it doesn't make any difference if you change
> the port number, you'll get the same result. Using a different
> hostname which points to the same IP-address doesn't change
> anything either, but using a different hostname *does*, strangely
> enough.


More weirdness. It seems you only get this error if the first and
second connect() are roughly less than 0.9 seconds apart. If it's more
than 0.95 seconds, you always get:

(10035, 'The socket operation could not complete without blocking')
10035 = EWOULDBLOCK WSAEWOULDBLOCK

As select() will do the obvious, and just report the refused connection
as non-writeable forever, that means you're never going to see a
WSAEINVAL error if you only use connect() a second time when the socket
is actually writeable.


Robert Amesz

Panu A Kalliokoski

unread,
May 31, 2001, 5:13:32 AM5/31/01
to
Robert Amesz <rcameszR...@dds.removethistoo.nl> wrote:
> I've recently been doing a little work with sockets, more in particular
> non-blocking sockets, and I'm sorry to say the standard Python

I'd say non-blocking sockets are easier to use than blocking ones, but
usually you don't need them for anything. The only problems with
non-blocking sockets are that they can give you interesting error codes
that you have to take action on, and the errors returned vary from
platform to platform. Usually, the right solution is to use select() with
possible timeouts, not non-blocking sockets. That's why I've made the
selecting package (http://sange.fi/~atehwa-u/selecting-0.8/)

Non-blocking sockets only make sense if you have a background task which
you want to accomplish while listening to the sockets. For this special
case, threads are better suited.

> documentation isn't really too helpful here. I feel this is a mistake:
> without documentation people like me will experiment to find out how

I'm not really one of the Python people, but I know the issues on the
socket API are more of OS than language information. The socket
abstraction in Python (at least when I was using it) is not nearly a good
one. Maybe that should be corrected. Anyway, good information on sockets
can be found all over the net and from the Un*x manual (man 2 socket, man
4 socket).

I hope not every language project would have to make new socket tutorials,
as the situation is now. That's a lot of added, overlapping work.

> I've also studied timeoutsocket.py for some hints and pointers about
> socket behaviour, and this is a good source of information about some
> of the quirks of non-blocking sockets, so I'd like to thank
> Timothy O'Malley for that.

Please have a look at the asyncore package (distributed with Python) and
selecting (a pointer to which I gave above).

Panu Kalliokoski

0 new messages