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

Dearest Monks, I've been googling all morning and can't quite get the following to work as needed. I have a server that I am successfully connecting to with IO::Socket::INET. I need to connect to this server every 5 minutes and make sure that I am getting a particular string from it when I send it a particular command. The response to the command could take up to 5 minutes. Again, the connecting to the server and the sending of the command part is working fine. I'm stuck at how to monitor the server after sending the command for 5 minutes and waiting to see if it responds with the particular string. It needs to timeout after five minutes with an error if the string isn't received. Here is the code so far. All it is currently doing is connecting to the server and printing out any output received.
#!/usr/bin/perl use IO::Socket::INET; $host = shift; $port = shift; $MySocket = new IO::Socket::INET->new("$host:$port") or die "connect: +$@"; $command = "EDMSFT\|ISL\|\@\n"; $MySocket->send($command); while (1) { $MySocket->recv($text,128); print "$text\n"; } close $MySocket;

Replies are listed 'Best First'.
Re: Using IO::Socket
by Earindil (Beadle) on Oct 12, 2004 at 15:55 UTC
    I believe I solved my problem with the following code. It seems to be working well with testing. See any issues?

    #!/usr/bin/perl use IO::Socket::INET; use IO::Select; $timeout = 180; $host = shift; $port = shift; $string = shift; my $TIMEDOUT, $counter; $MySocket = new IO::Socket::INET->new("$host:$port") or die "connect: +$@"; $command = "EDMSFT\|$string\|\@\n"; $MySocket->send($command); while (($text !~ m/$string/)&&(!$TIMEDOUT)&&($counter<30)) { if( () = IO::Select->new($MySocket)->can_read($timeout) ) { my $sender = $MySocket->recv( $text, 1024, 0 ); $counter++; ### TIMEOUT if counter hits 30 without a match } else { $TIMEDOUT=1; } } if ((!$TIMEDOUT)&&($counter<30)) { print "GOOD\n"; } else { print "TIMEDOUT\n"; } close $MySocket; exit;
      Pretty good in fact, but it has a few flaws:
      • A connect can take a long time too
      • Writes can block too
      • The read can be satisfied in several chunks
      • Operations can be interrupted

      Here is a (only sligtly tested) version of how I would do it when using IO::Socket::INET and IO::Select (in reality I would use POE). Like you I will assume the protocol does pipelining, otherwise you should immediately start checking for readability to drain pending output that can block the server. I'm also assuming that the server finishes with EOF, otherwise you might already want to start checking for your target string inside the read loop.

      #! /usr/bin/perl -w use strict; use IO::Socket::INET; use IO::Select; use POSIX qw(EWOULDBLOCK EINTR); my $timeout = 180; my ($host, $port, $string) = @ARGV; my $too_late = time()+$timeout; my $socket = IO::Socket::INET->new(PeerAddr => "$host:$port", Blocking => 0) || die "Could not connect to $host:$port: $!\n"; # Write (also handles connect, since being connected is a writability +event) $SIG{PIPE} = "IGNORE"; my $command = "EDMSFT|$string|\@\n"; my $select = IO::Select->new($socket); while ($command ne "") { my $left = $too_late - time(); if ($left <= 0) { print "TIMEDOUT\n"; exit; } next unless $select->can_write($left); if (defined(my $rc = syswrite($socket, $command))) { substr($command, 0, $rc, ""); } else { next if $! == EWOULDBLOCK || $! == EINTR; die "Error writing to $host:$port: $!\n"; } } # Read my $text = ""; while (1) { my $left = $too_late - time(); if ($left <= 0) { print "TIMEDOUT\n"; exit; } next unless $select->can_read($left); my $rc = sysread($socket, $text, 4096, length $text); if (!$rc) { last if defined $rc; # EOF next if $! == EWOULDBLOCK || $! == EINTR; die "Error reading from $host:$port: $!\n"; } } $text =~ /\Q$string\E/ || die "Unexpected answer from $host:$port: $text"; print "GOOD\n";
        Thanks much. I'll implement what I can use and see how it goes. Really appreciate the input. The server should never send an EOF. It's a continuous stream of data. The query string was added because when the app is broken, it either outputs nothing or outputs one short heartbeat every 30 secs or so (that doesn't contain the query string).
Re: Using IO::Socket
by Zaxo (Archbishop) on Oct 12, 2004 at 15:28 UTC

    The canonical way to open an IO::Socket::INET instance to do what you want is,

    my $MySocket = new IO::Socket::INET->new( PeerAddr => $host, PeerPort => $port, Proto => 'tcp', # or whatever Timeout => 300); $MySocket->connected() or die "No connection to $host:$port";
    Note that a socket which fails to connect does not return a false value.

    After Compline,
    Zaxo

      "Note that a socket which fails to connect does not return a false value."

      This is definitely wrong, otherwise why the following worked for me:

      use IO::Socket::INET; use strict; use warnings; IO::Socket::INET->new(Proto => "tcp", PeerAddr => "123.123.123.123", P +eerPort => 123, Timeout => 10) || die "failed to connect";

      If whatever before 'or' was not evaluated to false, why should the die get evaluated, and print the message on screen for me?

        You're right. I'm not sure what got me confused about that. Socket configure can return undef.

        After Compline,
        Zaxo

      I was under the impression (from much reading) that the Timeout in this case only was for the opening of the connection, not on the recv or sends.