我想读取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 错误。perl
的eof()
调用read()
并意味着执行缓冲 I/O。你不能真正将它用于非阻塞 I/O。在您的示例中,第一个block1
被读取eof()
,然后select()
等待sleep 1
,第二个block1
被读取read()
,返回两个block1
s。
在这里,您最好使用阻塞 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);