制作一个24/7运行并从命名管道读取的Perl守护程序

n0p*_*0pe 7 perl daemon syslog named-pipes

我正在尝试使用perl制作日志分析器.分析器将在AIX服务器的后台运行24/7,并从syslog将日志定向到的管道(从整个网络)读取.基本上:

logs from network ----> named pipe A -------->   | perl daemon
                  ----> named pipe B -------->   | * reads pipes
                  ----> named pipe c -------->   | * decides what to do based on which pipe
Run Code Online (Sandbox Code Playgroud)

因此,例如,我希望我的守护程序能够配置为mail root@domain.com写入的所有日志named pipe C.为此,我假设守护进程需要有一个哈希(perl的新内容,但这似乎是一个合适的数据结构),它可以在运行中进行更改并告诉它如何处理每个管道.

这可能吗?或者我应该创建一个.conf文件/etc来保存信息.像这样的东西:

namedpipeA:'mail root@domain.com'
namedpipeB:save:'mail user@domain.com'
Run Code Online (Sandbox Code Playgroud)

所以从中获取任何东西A将被邮寄到,root@domain.com并且所有内容都B将保存到日志文件中(通常是这样)并且它将被发送到user@domain.com

看到这是我第一次使用Perl和我第一次创建一个守护进程,我是否仍然坚持KISS校长这样做?还有,我应该遵守哪些惯例?如果您在回复时可以考虑到我缺乏知识,那将是最有帮助的.

jro*_*way 18

我将介绍你的部分问题:如何编写一个处理IO的长期运行的Perl程序.

编写处理许多同时IO操作的Perl程序的最有效方法是使用事件循环.这将允许我们为事件编写处理程序,例如"命名管道上出现一行"或"电子邮件已成功发送"或"我们收到SIGINT".至关重要的是,它允许我们在一个程序中组成任意数量的这些事件处理程序.这意味着您可以"多任务"但仍然可以轻松地在任务之间共享状态.

我们将使用AnyEvent框架.它允许我们编写事件处理程序,称为观察程序,它将与Perl支持的任何事件循环一起使用.您可能不关心使用哪个事件循环,因此这种抽象可能与您的应用程序无关.但它将让我们重用CPAN上可用的预先编写的事件处理程序; AnyEvent :: SMTP用于处理电子邮件,AnyEvent :: Subprocess用于与子进程交互,AnyEvent :: Handle用于处理管道,等等.

基于AnyEvent的守护进程的基本结构非常简单.你创建一些观察者,进入事件循环,然后......就是这样; 事件系统完成其他所有事情.首先,让我们编写一个程序,每五秒打印一次"Hello".

我们首先加载模块:

use strict;
use warnings;
use 5.010;
use AnyEvent;
Run Code Online (Sandbox Code Playgroud)

然后,我们将创建一个时间观察器或"计时器":

my $t = AnyEvent->timer( after => 0, interval => 5, cb => sub {
    say "Hello";
});
Run Code Online (Sandbox Code Playgroud)

请注意,我们将计时器分配给变量.只要$t在范围内,这就使计时器保持活动状态.如果我们说undef $t,那么计时器将被取消,并且永远不会调用回调.

关于回调,这是sub { ... }cb =>,这就是我们处理事件的方式.发生事件时,将调用回调.我们做了我们的事情,返回,并且事件循环继续根据需要调用其他回调.您可以在回调中执行任何操作,包括取消和创建其他观察者.只是不要拨打阻止电话,system("/bin/sh long running process")或者my $line = <$fh>或者sleep 10.阻挡的任何东西都必须由观察者完成; 否则,在等待该任务完成时,事件循环将无法运行其他处理程序.

现在我们有了一个计时器,我们只需要进入事件循环.通常,您将选择要使用的事件循环,并以事件循环文档描述的特定方式输入它. EV是一个很好的,你通过电话输入它EV::loop().但是,我们将让AnyEvent通过编写来决定使用什么事件循环AnyEvent->condvar->recv.不要担心这样做; 这是一个成语,意思是"进入事件循环,永不回归".(当你阅读关于AnyEvent的内容时,你会看到很多关于条件变量或condvars的内容.它们对于文档和单元测试中的示例很有用,但你真的不希望在你的程序中使用它们.如果你"在.pm文件中使用它们,你做的事情非常错误.所以只是假装它们现在不存在,并且你从一开始就编写非常干净的代码.这将使你领先于许多CPAN作者!)

所以,只是为了完整性:

AnyEvent->condvar->recv;
Run Code Online (Sandbox Code Playgroud)

如果你运行该程序,它将每隔五秒打印一次"Hello",直到宇宙结束,或者更可能的是,你用控制c杀死它.这个问题很简单,你可以在打印"Hello"之间的五秒钟内做其他事情,而你只需添加更多的观察者即可.

所以,现在从管道读取.AnyEvent使用AnyEvent :: Handle模块使这很容易.AnyEvent :: Handle可以连接到套接字或管道,只要有数据可供读取,它就会调用回调.(它也可以执行非阻塞写入,TLS和其他东西.但我们现在不关心它.)

首先,我们需要打开一个管道:

use autodie 'open';
open my $fh, '<', '/path/to/pipe';
Run Code Online (Sandbox Code Playgroud)

然后,我们用AnyEvent :: Handle包装它.创建Handle对象后,我们将它用于此管道上的所有操作.你可以完全忘记$fh,AnyEvent :: Handle将直接处理它.

my $h = AnyEvent::Handle->new( fh => $fh );
Run Code Online (Sandbox Code Playgroud)

现在我们可以用来$h从管道中读取线条:

$h->push_read( line => sub {
    my ($h, $line, $eol) = @_;
    say "Got a line: $line";
});
Run Code Online (Sandbox Code Playgroud)

当下一行可用时,这将调用打印"获得一行"的回调.如果要继续读取行,则需要使函数将自身推回到读取队列,如:

my $handle_line; $handle_line = sub {
    my ($h, $line, $eol) = @_;
    say "Got a line: $line";
    $h->push_read( line => $handle_line );
};
$h->push_read( line => $handle_line );
Run Code Online (Sandbox Code Playgroud)

这将读取行并调用$handle_line->()每一行,直到文件关闭.如果你想提前停止阅读,这很容易...... push_read在这种情况下再也不要了.(您不必在行级读取;您可以要求在任何字节可用时调用您的回调.但这更复杂,并留给读者练习.)

所以现在我们可以将它们组合成一个处理读取管道的守护进程.我们想要做的是:为行创建一个处理程序,打开管道并处理这些行,最后设置一个信号处理程序来干净地退出程序.我建议采用OO方法来解决这个问题; 使每个动作("从访问日志文件处理行")使用startstop方法的类,实例化一堆动作,设置信号处理程序以干净地停止动作,启动所有动作,然后进入事件循环.这是很多与这个问题没有关系的代码,所以我们会做一些更简单的事情.但在设计程序时请记住这一点.

#!/usr/bin/env perl
use strict;
use warnings;
use AnyEvent;
use AnyEvent::Handle;
use EV;

use autodie 'open';
use 5.010;

my @handles;

my $abort; $abort = AnyEvent->signal( signal => 'INT', cb => sub {
    say "Exiting.";
    $_->destroy for @handles;
    undef $abort; 
    # all watchers destroyed, event loop will return
});

my $handler; $handler = sub {
    my ($h, $line, $eol) = @_;
    my $name = $h->{name};
    say "$name: $line";
    $h->push_read( line => $handler );
};

for my $file (@ARGV) {
    open my $fh, '<', $file;
    my $h = AnyEvent::Handle->new( fh => $fh );
    $h->{name} = $file;
    $h->push_read( line => $handler );
}

EV::loop;
Run Code Online (Sandbox Code Playgroud)

现在你有一个程序从任意数量的管道中读取一行,打印在任何管道上接收的每一行(前缀为管道的路径),并在按下Control-C时完全退出!

  • 我还应该注意,当写入STDOUT时,你还应该使用AnyEvent :: Handle.STDOUT可以附加到未读取的管道,然后您的程序将阻止.但这对于"基于事件的编程介绍"非常深奥:) (2认同)