Flame has asked for the wisdom of the Perl Monks concerning the following question:

Having failed to locate anything useful via search, chatterbox or the IO::Select, IO::Socket, IO::Socket::INET and Socket docs, I'm forced to waste a SOPW question on it. What is the best/easiest way to detect when the other side of a socket has closed?

Some background: I'm writing a Perl server to work with Java client(s) using IO::Select and IO::Socket::INET. I read from the socket using sysread. I know that IO::Select concludes that a closed socket is constantly waiting to be read from... how I ran into that information I no longer recall, however I'm not entirely certain how to tell if it's closed or not.

I have tried $handle->connected as described in IO::Socket, however, it never seems to return false.

I realize I could have written this a little more clearly, but I'm afraid this will have to do... Thank you for reading, even if you don't know the answer. This is my first foray into networking and communications aside from writing CGI/mod_perl scripts (and we all know those don't really qualify) so any generic tips would be appreciated... In fact, while you're here, anyone know how latency/LAG would affect these kinds of things? (Ie: would sysread give up if lag held up part of a message?)

Thank you

My code doesn't have bugs, it just develops random features.

Flame ~ Lead Programmer: GMS (DOWN) | GMS (DOWN)

Replies are listed 'Best First'.
Re: Detecting a closed socket
by edan (Curate) on Aug 05, 2003 at 07:11 UTC


    I have written servers in perl, and the following works for me, to determine whether a socket is closed:

    As you said, IO::Select will indicate that the socket can_read, so you'll go ahead and read from it, using sysread. sysread returns the number of bytes that were read, or undef on error, or 0 if end-of-file was reached. That last thing basically means that the socket was closed - or at least, you're not going to read from it anymore. So, your logic will look like the following untested, pseudo-code snippet:

    my $bytes_read = $socket->sysread($buf, $max_read_len); if (not defined $bytes_read) { print "ack! error on the socket\n"; } elsif ($bytes_read == 0) { print "the socket was closed\n"; } else { print "woo hoo! I read $bytes_read bytes from the socket...\n"; }

    Hope that helps.

Re: Detecting a closed socket
by sgifford (Prior) on Aug 05, 2003 at 05:25 UTC

    It depends on what you mean by "closed".

    If you want to detect the case where the other end has properly disconnected, it's pretty easy: try to select on the filehandle (the 4-argument form), it will return as being ready, then an attempt to read will return eof.

    If you want to include the case where the other end may have just fallen off the 'net without properly disconnecting, you have to actually write some data to the socket. TCP doesn't exchange periodic state messages, so it's impossible to tell if an absent client is disconnected or just silent. Many protocols provide NOOP-type functions for this purpose. The exception is if you use the SO_KEEPALIVE option, in which case you'll be informed of a timeout after an hour or two.

    sysread shouldn't give up unless it knows there is data pending, but it receives nothing for a long time. This doesn't tend to happen in real life unless the other end really has gone away for a while (a few minutes). The details may be system dependent; you're getting into TCP/IP Illustrated territory, and I don't feel like re-reading the (verbose) details.

Re: Detecting a closed socket
by LameNerd (Hermit) on Aug 05, 2003 at 04:52 UTC
    I don't understand why you need to determine if the socket is "closed" or not. If you look at this simple example from Q&A you see that the server never "opens" or "close" a socket but it does create a socket and "accepts" connections from clients and then reads from the socket. The server detects that client has stopped sending data when the client terminates and its socket is destoryed. For a more complicated client/server system the client could signal the server that it has finished sneding data by sending some sort of flag or by telling the server how much data to expect. Also for a more than trival server you will need to fork the server after it does the accept because while it is reading from the socket no other clients will be able to connect. The clients can only connect when the server is doing an accept. ... I think :)
      Heh, it's a little more complex than a simple file-server, and I have it working, just having a hard time telling when the other side leaves... the protocol I'm using has no 'goodbye' (perhaps I should do something about that)

      My code doesn't have bugs, it just develops random features.

      Flame ~ Lead Programmer: GMS (DOWN) | GMS (DOWN)

Re: Detecting a closed socket
by kschwab (Vicar) on Aug 05, 2003 at 14:06 UTC
    Using sysread(), your indication that the other end has closed the connection would be a "successful sysread of zero bytes". This works when the other end does an orderly close() or shutdown() of the send side of the socket. So, you would have to test the return value of sysread() for undef (error) and zero( end of file, a.k.a, client closed connection ).

    In the event that something unusual happens ( perhaps the client crashed ?), keepalives could be helpful. ($sockobj->sockopt(SO_KEEPALIVE,1);)

Re: Detecting a closed socket
by Anonymous Monk on Aug 06, 2003 at 01:27 UTC
    You may be better off using Net::Server for creating any server that interfaces with a Java program... but I don't remember if $! gets an error if <> finds out that the socket got closed. Experiment!!!
Re: Detecting a closed socket
by MarkM (Curate) on Aug 06, 2003 at 04:33 UTC

    Others have offered pieces of the puzzle. I'll try and offer you the complete solution:

    Stream sockets (UNIX or INET) are bi-directional pipes. The pipe may be closed for reading or writing at either end (see shutdown() in perlfunc). When one end close()'s the socket, the pipe is closed for both reading and writing at that end.

    If a socket is read from that was closed for writing at the remote end, the read system call will return 0 (see sysread() in perlfunc).

    If a socket is written to that was closed for reading at the remote end, the write system call will return EPIPE (and the calling process may be forced to handle or ignore SIGPIPE).

    IO::Select (really select() or poll()) should be used as an 'event ready' indicator, not a 'bytes' indicator.

    In your latest response, you suggested that you would be verifying that write() returns the number of bytes that you expect it to. This is not proper, as the write system call may append the bytes to a system buffer, and return the number of bytes 'written' immediately. The bytes have not reached their destination at this point, and the socket could still close for reading at the remote end before your bytes reach it.

    The subject of ensuring that records are transmitted successfully, completely, and that separate connections are synchronized is a complicated subject that I won't begin to discuss here.

    Cheers, and good luck,

      I seem to have confused you a little. I'm not all that concerned about the sucess of the transmissions themselves, so I have no checks on any of the writing to the sockets, the only problem I had was a set of dead sockets that my server was holding on to un-necessaraly. True I could add greater destinction between what went wrong in the read set, and I may do that at some point, but as it seems to work now, I see no need. As for the IO::Select thing, it is only being used as a "this socket has something to say" indicator. The expected length is determined by a 4 digit number that prefixes the message.

      It's also possible I'm mis-understanding your advice, if so please explain more fully.

      My code doesn't have bugs, it just develops random features.

      Flame ~ Lead Programmer: GMS (DOWN) | GMS (DOWN)

        UPDATE: The poster was using the return of read(), not write(). This still isn't safe under all circumstances, but it should be safe as long as the socket is blocking, and there is no requirement for the server application to switch between multiple clients. In the servers that I normally write, I always have to switch between multiple clients, therefore I always choose to use non-blocking sockets, with all read() and write() operations performed by a buffering layer.

        Detecting failure on write() only allows you to clean up 'dead' sockets when you get around to writing to the sockets. This can be sub-optimal if write() is issued infrequently.

        Also, write() only guarantees that the bytes make it to a system buffer, it does not guarantee that the socket is still 'open'. If the socket is blocking (the default), write()'s larger than the size of the system buffer will block, holding the entire server process up. If the socket is non-blocking (see IO::Handle::blocking()) write()'s may return with fewer bytes (usually, the room left in the system buffer before it is filled), but this does not mean that the socket is closed. It just means you have to complete the write() at a later time.

        Somebody else suggested that you use Net::Server. The reason for this suggestion is that socket manipulation is not always intuitive, and it is frequently more complex than most people want to understand. Server applications implemented using logic like "syswrite(4) must return 4" may work in the present, because 4 is significantly less than the size of a system buffer, but what happens when more bytes are written, and the system buffer gets filled? The server can fail, and the failure can be difficult to reproduce or solve later.


Re: Detecting a closed socket
by Flame (Deacon) on Aug 06, 2003 at 01:30 UTC

    Ok, I think I've got my solution then:

    unless(sysread($handle,$count,4) == 4 && $count =~ m/^\d{4}$/ && + sysread($handle,$message,$count) == $count){ # print "Error/Disconnect\n"; return; }

    I've tested it and it seems to work. Thanks to everyone for the input.

    My code doesn't have bugs, it just develops random features.

    Flame ~ Lead Programmer: GMS (DOWN) | GMS (DOWN)

A reply falls below the community's threshold of quality. You may see it by logging in.