2

I'm trying - and failing - to get a perl server to detect and get rid of connection with a client who broke the connection. Everywhere I looked, the suggested method is to use the socket's ->connected() method, but in my case it fails.

This is the server absolutely minimized:

#!/usr/bin/perl
use IO::Socket;
STDOUT->autoflush(1);


my $server = new IO::Socket::INET (
                                Listen => 7,
                                Reuse   => 1,
                                LocalAddr => '192.168.0.29',
                                LocalPort => '11501',
                                Proto => 'tcp',
                                 );
die "Could not create socket: $!\n" unless $server;

print "Waiting for clients\n";

while ($client = $server->accept()) {
        print "Client connected\n";
        do {
                $client->recv($received,1024);
                print $received;
                select(undef, undef, undef, 0.1); # wait 0.1s before next read, not to spam the console if recv returns immediately
                print ".";
        } while( $client->connected() );
        print "Client disconnected\n";
}

I connect to the server with Netcat, and everything works fine, the server receiving anything I send, but when I press ctrl-C to interrupt Netcat, 'recv' is no longer waiting, but $client->connected() still returns a true-like value, the main loop never returns to waiting to the next client.

(note - the above example has been absolutely minimized to show the problem, but in the complete program the socket is set to non-blocking, so I believe I can't trivially depend on recv returning an empty string. Unless I'm wrong?)

SF.
  • 13,549
  • 14
  • 71
  • 107
  • @ikegami: [1](https://stackoverflow.com/questions/14061109/how-do-you-know-a-socket-client-disconnected), [2](https://stackoverflow.com/questions/39447554/check-if-socket-is-connected-before-sending-data), [3](https://www.perlmonks.org/?node_id=999744) and countless others use it. – SF. Aug 30 '21 at 14:16
  • `do .. while recv` means you call `recv` after going through the look, which makes no sense. – ikegami Aug 30 '21 at 14:16
  • Re "*countless others use it.*", 1) so what? 2) I deleted the comment as soon as I posted it (at least 10 minutes before your reply) when I realized I was looking at the wrong docs. – ikegami Aug 30 '21 at 14:19
  • 1) so... are they all horribly wrong? Or are they doing something I missed, happily using it? – SF. Aug 30 '21 at 14:21
  • Yeah. I'm not sure why you'd ever want to use `connected`. See answer. – ikegami Aug 30 '21 at 14:24

1 Answers1

3

connected can't be used to reliably learn whether the peer has initiated a shutdown. It's mentioned almost word for word in the documentation:

Note that this method considers a half-open TCP socket to be "in a connected state". [...] Thus, in general, it cannot be used to reliably learn whether the peer has initiated a graceful shutdown because in most cases (see below) the local TCP state machine remains in CLOSE-WAIT until the local application calls "shutdown" in IO::Socket or close. Only at that point does this function return undef.

(Emphasis mine.)

If the other end disconnected, recv will return 0. So just check the value returned by recv.

while (1) { 
   my $rv = $client->recv(my $received, 64*1024); 
   die($!) if !defined($rv);     # Error occurred when not defined.
   last if $received eq "";      # EOF reached when zero.

   print($received);
}

Additional bug fix: The above now calls recv before print.

Additional bug fix: Removed the useless sleep. recv will block if there's nothing received.

Performance fix: No reason to ask for just 1024 bytes. If there's any data available, it will be returned. So you might as well ask for more to cut down on the number of calls to recv and print.

Note that even with this solution, an ungraceful disconnection (power outage, network drop, etc) will go undetected. One could use a timeout or a heartbeat mechanism to solve that.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • How would I go about implementing it? – SF. Aug 30 '21 at 14:17
  • it appears like the `!rv` condition is always fulfilled, even after a successful read. If I remove it, recv ceases blocking once I abort the client. – SF. Aug 30 '21 at 14:49
  • Sorry, I'm more used to using `sysread` thatn `recv` (which you could use here instead of `recv`), and I thought they had the same return value. Fixed. – ikegami Aug 30 '21 at 16:07