René Nyffenegger
René Nyffenegger

Reputation: 40499

How can I deteremine if I'd read something from a socket?

The following piece of code is supposed to read a few, undetermined in number, lines from a socket.

use warnings;
use strict;

use IO::Socket::INET;

my $server = shift;
my $port   = shift;

my $sock  = new IO::Socket::INET (
               PeerAddr    => $server,
               PeerPort    => $port,
               Proto       => 'tcp',
               Timeout     =>  1,
               Blocking    =>  0
           ) 
           or die "Could not connect";

while (my $in = <$sock>) {
  print "$in";
}

print "Received last line\n";

Unfortunately, the $in = <$sock> part is blocking although I have set Blocking => 0 and the server does not send any more text. Hence, Received last line won't be printed.

So, I tried to improve the behavior with use IO::Select:

use warnings;
use strict;

use IO::Socket::INET;
use IO::Select;

my $server = shift;
my $port   = shift;

my $sock  = new IO::Socket::INET (
               PeerAddr    => $server,
               PeerPort    => $port,
               Proto       => 'tcp',
               Timeout     =>  1,
               Blocking    =>  1
           ) 
           or die "Could not connect";

my $select = new IO::Select;
$select -> add($sock);

sleep 1;
while ($select -> can_read) {
  my $in = <$sock>;
  print $in;
}

This second approach only prints the first sent line, then seems to block for ever.

Since I have seen such examples working, I believe the problem is Windows, on which I am trying to run these scripts.

Is there a way how I can achieve a non blocking read?

Upvotes: 0

Views: 82

Answers (1)

ikegami
ikegami

Reputation: 385847

When using select (used by can_read), it defies the purpose to follow up with blocking IO. You must also avoid buffering IO because the system (i.e. select) doesn't know about any data in your library's buffers. This means you can't mix select with read, readline (aka <> and <$fh>) and eof. You must use sysread.

my %clients;
for my $fh ($select->can_read()) {
   my $client = $clients{$fh} //= {};
   our $buf; local *buf = $client->{buf} //= '';   # alias my $buf = $client->{buf};
   my $rv = sysread($sock, $buf, 64*1024, length($buf));
   if (!defined($rv)) {
      my $error = $!;
      $select->remove($fh);
      delete($clients{$fh});
      # ... Handle error ...
      next;
   }

   if (!$rv) {
      $select->remove($fh);
      delete($clients{$fh});
      # ... Handle EOF ...         # Don't forget to check if there's anything in $buf.
      next;
   }

   ... remove any complete messages from $buf and handle them ...
}

If you want to read a line at a time, you'd use

while ($buf =~ s/^([^\n]*)\n//) {
   process_msg($client, $1);
}

Upvotes: 1

Related Questions