如何在 Perl 中捕获 ctrl+c 并关闭文件描述符

How to catch ctrl+c and close file descriptor in Perl

我有 Perl 脚本 - 简单的服务器。循环等待connect,写入新的光盘文件。当按下 ctrl + c 时,服务器应该停止。退出前,我想关闭所有文件描述符。这并不容易,因为终端显示错误。 是的,我读了这个线程: How can I check if a filehandle is open in Perl?
但这对我不起作用。

这是我的主循环:

my $my_socket = new IO::Socket::INET(
    LocalHost => $local_host,
    LocalPort => $local_port,
    Proto     => 'tcp',
    Listen    => 5,
    Reuse     => 1
);
die "Couldn't create: my_socket $!n " unless $my_socket;
print "Send files.. \n";
while(1) {
    my $accepter = $my_socket->accept();
    my $count    = 0;
    #print "$directory.$save_dir/$my_data";
    $datetime    = localtime();
    $datetime    =~ s/(?<=\w)\s(?=\w)/_/g;

    open my $fh, '>', "$direc/$save_dir/$datetime"
        or die "Couldn't open the file";
    while(<$accepter>){
        chomp;
        #last if $count++ ==10;
        #print($accepter);
        say $fh $_;
    }
    $SIG{'INT'} = sub {
        print "Caught One!\n";
        close $fh;
    }; #It gives me error. Close fd which is not opened.
}
#print "Received. End \n";
#close $fh;
close $my_socket;

信号处理程序中的代码关闭该文件句柄——并让循环继续。因此,下一次该文件句柄确实已关闭,因此您会收到警告。 (如果问题描述得当,我希望在下一次打印时首先出现问题,但可能有避免这种情况的原因。)

修复,简而言之 -- 在信号处理程序中设置一个标志,仅此而已。在代码中的合适位置检查它,如果已设置,则关闭文件并通过 last. 退出循环(或执行其他操作,视情况而定用于您的代码。)

不过,关于信号处理程序,我还想多说几句。 %SIG 是一个非常全球化的生物。通过设置 $SIG{INT},您已经为解释器中的所有代码更改了它。

为什么不使用 local $SIG{INT} 呢?然后它只在定义它的范围内被改变;参见 local。我会把这个定义拉到所有可能的循环之外。 (如果你真的想让它成为全球性的,就把它放在开头,这样它就响亮而清晰,而不是隐藏起来。)

所以像

SOME_SCOPE: 
{ 
    my $got_signal;
    local $SIG{INT} = sub {
        #say "Caught: @_";
        $got_signal = 1;
    };

    while (1) {
        ...
        open my $fh, '>', ...  or die $!;

        while (<$accepter>) {
            ...
            if ($got_signal) { 
                close $fh;
                # $got_signal = 0;  # if used again (reset it)
                last;
            }
            say $fh $_;
        }
        ...
    }
};

这仍然是一个草图,即使它在更简单的情况下也能正常工作。我必须假设一些事情,首先,您的代码中还有很多事情要做。如果包含完整的可运行示例可以帮助让我知道,我可以添加它。


不能在信号处理程序的子程序中执行 last(它不在循环内,即使它在代码中定义)。通常,至少可以说,在 sub 中使用 last 不是一个好主意,因为它会导致代码混乱和不透明。它还具有非常特殊的行为

last cannot return a value from a block that typically returns a value, such as eval {}, sub {}, or do {}. It will perform its flow control behavior, which precludes any return value.

(它也带有警告)在这种情况下,您不需要 return 一个值,但是(即使它有效)您绝对不想这样做 超出信号处理程序。

请注意,即使在问题中有链接,也可以使用 Scalar::Util::openhandle 检查文件句柄是否打开。这可以在这段代码中派上用场。

如果您希望服务器在按下 Ctrl-C 时真正退出,只需在中断处理程序中放置一个 exit 语句即可。这将在 Perl 退出时自动关闭所有文件句柄。

如果您希望读取循环在按下 Ctrl-C 时终止,但程序将继续执行,请执行此操作。但是我可能会建议 Ctrl-\ 作为发送 SIGQUIT 信号的更好选择。大多数人不希望从 Ctrl-C 中优雅地退出;他们希望立即停止。

our $doloop = 1;
$SIG{QUIT} = sub { $doloop = 0; };

while ($doloop) { ... }
$SIG{QUIT} = "IGNORE";

cleanup;
exit;

如果您需要在尝试关闭文件句柄之前检查文件句柄是否打开,只需使用 -e。对于其他类型的开放式测试,请参阅 perlfunc -X。

close $fh if -e $fh;

HTH