STDINの読み取りとタイムアウトの設定

STDINの読み取りとタイムアウトの設定

STDINを読みたいのですが、5秒を超えてはいけません。それから、これまでに読んだデータを処理したいと思います。

selectまさにこれのために設計されているようです。入力があるかタイムアウトするまで待ちます。

入力がある場合にのみ非ブロックコンテンツを読み取る必要があります。

だから私はこれがうまくいくと思います:

#!/usr/bin/perl -w

use strict;
use Fcntl;

open(my $in, "<&", "STDIN") or die;
my $buf = "";
my $readsize = 10;

# Make $in non-blocking
fcntl($in, &F_SETFL, fcntl($in, &F_GETFL, 0) | &O_NONBLOCK);
while(not eof($in)) {
    my $starttime = time();
    my $rin = my $win = my $ein = '';
    vec($rin, fileno($in),  1) = 1;
    while(time() < $starttime + 5 and length $buf < $readsize) {
        # Wait up to 5 seconds for input                                                               
        select($rin, $win, $ein, $starttime + 5 - time());
        # Read everything that is available in the inputbuffer                                         
        while(read($in,$buf,$readsize,length $buf)) {}
    }
    print "B:",$buf;
    $buf = "";
}

次のように実行すると:

(echo block1;sleep 1; echo block1; sleep 6;echo block2)|
  perl nonblockstdin.pl 

これらのブロックは互いにマージされます。ブロック2は6秒後に開始されるため、2つのブロックでなければなりません。

私は何が間違っていましたか?

答え1

いくつかの問題:

  • 設定はO_NONBLOCKファイル記述子だけでなく、開いたファイルの説明にも影響します。たとえば、 を実行すると stdin が非ブロックになることがthat-script; catわかります。cat: -: Resource temporarily unavailablecat
  • 非ブロックI / Oを使用している場合、read()この時点で入力がなく、eofが設定されている場合、システムコールはEAGAINエラーを返します。呼び出しは、バッファリングされたI / Oの実行を意味しperlません。実際、非ブロックI/Oには使用できません。あなたの例では、最初のものはread、waited、2番目のものはreadで、両方のsを返します。eof()read()block1eof()select()sleep 1block1read()block1

たとえば、ここではI / Oブロックとタイムアウトを使用することをお勧めしますalarm()。非ブロックI / Oを使用してselect()代わりにeof()使用する場合、終了時にフラグが事前に設定されている場合はそれをクリアする必要があります(stdinで設定することは、stdinが他の共有sysread()プロセスと対話する可能性があるため、まだ悪い考えです)。read()O_NONBLOCKO_NONBLOCK

答え2

Stephenの提案を元にこれを思い出し、これまでは効果があるようです。

#!/usr/bin/perl -w                                                                                                                                   

use strict;

my $timeout = 2;
my $buf = "";
my $blocksize = 30;
my $readsize;
my $nread;
my $buflen;
my $alarm;
my $eof;

open(my $in, "<&", "STDIN") or die;

do {
    eval {
        local $SIG{ALRM} = sub { die "alarm\n" }; # NB: \n required                                                                                  
        alarm $timeout;
        # Add upto the next full block                                                                                                               
        $readsize = $blocksize - (length $buf) % $blocksize;
        do {
            $nread = sysread $in, $buf, $readsize, length $buf;
            $readsize -= $nread;
        } while($nread and $readsize);
        alarm 0;
    };
    if ($@) {
        die unless $@ eq "alarm\n";   # propagate unexpected errors                                                                                  
        $alarm = 1;
    } else {
        $alarm = 0;
    }
    print "B:$buf<\n";
    $buf = "";
    $eof = not ($nread or $alarm);
} while(not $eof);

関連情報