读取 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 秒后开始。

我究竟做错了什么?

答案1

一些问题:

  • 设置O_NONBLOCK影响打开文件的描述,而不仅仅是文件描述符。例如,如果您运行that-script; cat,您会看到cat: -: Resource temporarily unavailable,因为cat的 stdin 变为非阻塞
  • 如果使用非阻塞 I/O,read()如果此时没有输入并且设置了 eof,系统调用将返回 EAGAIN 错误。perleof()调用read()并意味着执行缓冲 I/O。你不能真正将它用于非阻塞 I/O。在您的示例中,第一个block1被读取eof(),然后select()等待sleep 1,第二个block1被读取read(),返回两个block1s。

在这里,您最好使用阻塞 I/O 并使用alarm()例如超时。如果使用非阻塞 I/O 和select(),请不要使用eof()和 usesysread()而不是read(),并确保O_NONBLOCK在退出时清除该标志(如果事先设置了该标志)(在 stdin 上设置仍然是一个坏主意,O_NONBLOCK因为 stdin 可以与其他进程共享)。

答案2

根据斯蒂芬的建议,我想出了这个,到目前为止似乎可行。

#!/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);

相关内容