Reputation: 2080
for the life of me, i can't seem to figure out how to get a standard TCP socket connection to reconnect after a disconnect, particularly in the context of an IO::Async::Loop
some basics:
#!/usr/bin/perl
use strict;
use warnings;
use Socket;
use IO::Async::Loop;
use IO::Async::Stream;
use IO::Socket;
use Time::HiRes qw(usleep);
# standard event loop
my $loop = IO::Async::Loop->new;
# notification service socket connection; we only write outgoing
my $NOTIFY = IO::Socket::INET->new(
PeerHost => $a_local_network_host,
PeerPort => $comm_port,
Proto => 'tcp',
Type => SOCK_STREAM,
ReuseAddr => 1,
Blocking => 0
) or warn("Can't connect to NOTIFY: $!\n");
setsockopt($NOTIFY, SOL_SOCKET, SO_KEEPALIVE, 1);
# main interface element via IO::Async
my $notifystream = IO::Async::Stream->new(
handle => $NOTIFY,
on_read => sub {
my ( $self, $buffref, $eof ) = @_;
# here's where we need to handle $eof if the remote goes away
if($eof) {
# i have tried several things here
usleep(200000); # give the remote service some milliseconds to start back up
# process fails if remote is not back up, so i know the timeout is 'good enough' for this test
# attempt to reconnect. have also tried recreating from scratch
$NOTIFY->connect("$a_local_network_host:$comm_port");
# this doesn't seem to have any effect
$self->configure(handle=>$NOTIFY);
}
}
);
$loop->add( $notifystream );
# kickstart the event loop
$loop->run;
### -- Meanwhile, elsewhere in the code -- ###
$notifystream->write("data for notification service\n");
in reality, there are many more things going on in the loop. i also have more sophisticated ways to test for socket closed or broken, further error handlers on the $notifystream, and a better timeout/backoff for reconnecting to the remote service, however this should show the main crux of what i'm doing.
when the remote server goes away for any reason, i'd like to attempt to reconnect to it without disrupting the rest of the system. in most cases the remote sends eof cleanly because it's intentionally rebooting (not my choice, just something i have to deal with), but i'd also like to handle other communication errors as well.
in practice, the above code acts as though it works, however the remote service no longer receives further write calls to the $notifystream. no errors are generated, the $notifystream happily takes further writes, but they are not delivered to the remote.
i have a feeling i'm doing this wrong. i'm not looking to rewrite the rest of the application's event loop, so please no 'just use AnyEvent'-type responses -- really hoping to gain a better understanding of how to reconnect/reuse/recreate the variables in use here (IO::Socket::INET and IO::Async::Stream) to compensate when a remote server is temporarily unavailable.
Any suggestions or references towards this goal are welcome. Thanks!
-=-=-=-=-
to summarize errors i have (and have not) received: if i leave no usleep, the reconnect (or recreation) of the base socket will fail due to the remote service being unavailable. if i attempt to recreate the socket from scratch and then 'configure' the stream, i get 'can't call method sysread on undefined' which leads me to believe the socket is not recreated correctly. at no time do the stream's built in 'on_read_error' or 'on_write_error' handlers fire, regardless of how much i write to the socket with the current code, although if i destroy the socket entirely these will generate an error. the socket simply seems to still be active after i know it has closed, and a reconnect does not seem to change anything. no errors are generated, but the socket is not being written to.
is there different syntax for reconnecting to an IO::Socket::INET socket? so far calls to ->connect() or rebuilding from scratch seem to be the only options for a closed connection, and neither seem to work.
Upvotes: 1
Views: 925
Reputation: 123649
You simply cannot connect an existing socket multiple times. You can only close the socket and create a new socket. This has nothing to do with IO::Socket::INET, IO::Async::Stream or even Perl but this is how the sockets API works.
In detail: The local socket actually never got disconnected, i.e. it is still configured to send data from a specific local IP address and port and to a specific address and port. Only, that the sending will no longer worked because the underlying TCP connection is broken or closed (i.e. FIN exchanged). Since there is no API to unbind and unconnect a socket the only way is to close it and create a new one which is unbound and unconnected until connect is called. This new socket then might or might not get the same file descriptor as the previous one.
Upvotes: 1