Sunday, June 08, 2008

Non-blocking sockets in VisualWorks - the problem

I want to be able to know immediately when the other end of a socket connection closed the socket when using TCP sockets in VisualWorks. I need this for my work on Slaps, the smalltalk LDAP library.

Here is something you can try to see the issue. Run the following code and your image will wait for an inbound socket connection on port 12345. Use "telnet localhost 12345" to connect, enter a few characters and hit enter (whereupon telnet sends the characters over the socket to your server). Now you'll hit the first halt in the code. At this point close the telnet session (+] and then "quit" works for me). Now in the debugger, just press proceed and you'll get the next halt pop up - no exception indicating that the socket has closed by telnet. You can keep on reading from the socket for ever, getting zero bytes each time. So how can I get an exception when telnet (or the other end of any TCP socket) has closed unexpectedly? This post is already too long so I'll put what I've found in another post.

| serverSocket connectionSocket |
[|buffer octetsTransferred readOctets actuallyRead|
serverSocket := SocketAccessor newTCPserverAtPort: 12345.
serverSocket listenFor: 10.
connectionSocket := serverSocket accept.
buffer := ByteArray new: 100.
octetsTransferred := connectionSocket
readInto: buffer
startingAt: 1
for: 100.
readOctets := buffer copyFrom: 1 to: octetsTransferred.
readOctets halt: 'Now close the client connection'.
octetsTransferred := connectionSocket
readInto: buffer
startingAt: 1
for: 100.
octetsTransferred halt: 'Any exception? Nope.'.
actuallyRead := connectionSocket writeFrom: readOctets.
actuallyRead halt: 'Any exception? Nope.']
ensure: [
connectionSocket notNil ifTrue: [connectionSocket close].
serverSocket notNil ifTrue: [serverSocket close]]


Anonymous said...

i thought a read of zero bytes on a socket implied that the far side had disconnected. see this all the time in badly written code, they do a read in a loop and dont exit if they read 0 bytes so it just goes into an infinite loop.

am i misunderstanding or perhaps confused?

Bruce said...

No, it's not so simple. It is quite possible to keep reading zero octets from a socket with a healthy socket, but where the other end is being quiet for some reason. The other end closing a socket is reported by an explicit result code from the socket library, as I'll explain.

Telf said...

In the READ(2) manpage it claims, "On success, the number of bytes read is returned (zero indicates end of file)". In the case of a healthy socket with O_NONBLOCK set and no data available right this minute, the read() will return -1 and set the error to EAGAIN.

It's a bit ugly, but it's POSIX and we are stuck with it. I would have done the end of file as an error and return zero indicating that more data might be available in future but way too late to change it now.

Kind of interesting the way the C language gradually intrudes on every other language huh?