logs from network ----> named pipe A --------> | perl daemon ----> named pipe B --------> | * reads pipes ----> named pipe c --------> | * decides what to do based on which pipe
因此,例如,我希望我的守护进程能够配置为将所有写入命名管道C的日志发送到root@domain.com.为此,我假设守护进程需要有一个哈希值(perl的新内容),但这似乎是一个适当的数据结构),可以在运行中进行更改,并告诉它如何处理每个管道.
这可能吗?或者我应该在/ etc中创建一个.conf文件来保存信息.像这样的东西:
namedpipeA:'mail root@domain.com' namedpipeB:save:'mail user@domain.com'
因此,从A获取任何内容将邮寄到root@domain.com,B中的所有内容都将保存到日志文件中(通常是这样)并将其发送到user@domain.com
看到这是我第一次使用Perl和我第一次创建一个守护进程,无论如何,我是否仍然坚持KISS校长?还有,我应该遵守哪些惯例?如果您在回复时可以考虑到我缺乏知识,那将是最有帮助的.
解决方法
编写处理许多同时IO操作的Perl程序的最有效方法是使用事件循环.这将允许我们为事件编写处理程序,例如“命名管道上出现一行”或“电子邮件已成功发送”或“我们收到SIGINT”.至关重要的是,它允许我们在一个程序中组成任意数量的这些事件处理程序.这意味着您可以“多任务”但仍然可以轻松地在任务之间共享状态.
我们将使用AnyEvent框架.它允许我们编写事件处理程序,称为观察程序,它将与Perl支持的任何事件循环一起使用.您可能不关心使用哪个事件循环,因此这种抽象可能与您的应用程序无关.但它将让我们重用CPAN上可用的预先编写的事件处理程序; AnyEvent::SMTP处理电子邮件,AnyEvent::Subprocess处理子进程,AnyEvent::Handle处理管道,等等.
基于AnyEvent的守护进程的基本结构非常简单.你创建一些观察者,进入事件循环,然后……就是这样;事件系统完成其他所有事情.首先,让我们编写一个程序,每五秒打印一次“Hello”.
我们从加载模块开始:
use strict; use warnings; use 5.010; use AnyEvent;
然后,我们将创建一个时间观察者,或“计时器”:
my $t = AnyEvent->timer( after => 0,interval => 5,cb => sub { say "Hello"; });
请注意,我们将计时器分配给变量.只要$t在范围内,这就可以使计时器保持活动状态.如果我们说undef $t,那么定时器将被取消并且永远不会调用回调.
关于回调,这是cb =>之后的子{…},这就是我们处理事件的方式.发生事件时,将调用回调.我们做了我们的事情,返回,并且事件循环继续根据需要调用其他回调.您可以在回调中执行任何操作,包括取消和创建其他观察者.只是不要进行阻止调用,例如system(“/ bin / sh long running process”)或my $line =< $fh>或睡觉10.任何阻挡必须由观察者完成;否则,在等待该任务完成时,事件循环将无法运行其他处理程序.
现在我们有了一个计时器,我们只需要进入事件循环.通常,您将选择要使用的事件循环,并以事件循环文档描述的特定方式输入它. EV是一个很好的,你通过调用EV :: loop()输入它.但是,我们将让AnyEvent通过编写AnyEvent-> condvar-> recv来决定使用什么事件循环.不要担心这样做;这是一个成语,意思是“进入事件循环,永不回归”. (当你读到AnyEvent时,你会看到很多关于条件变量或condvars的东西.它们对文档和单元测试中的例子都很好,但你真的不想在程序中使用它们.如果你在.pm文件中使用它们,你做的事情非常错误.所以只是假装它们现在不存在,你会从一开始就编写非常干净的代码.这会让你领先于很多CPAN作者!)
所以,只是为了完整性:
AnyEvent->condvar->recv;
如果你运行该程序,它将每隔五秒打印一次“Hello”,直到宇宙结束,或者更可能的是,你用控制c杀死它.这有什么用的,你可以在打印“Hello”之间的那五秒内做其他事情,而你只需添加更多的观察者即可.
所以,现在从管道读取. AnyEvent使用AnyEvent :: Handle模块使这很容易. AnyEvent :: Handle可以连接到套接字或管道,只要有数据可供读取,它就会调用回调. (它也可以执行非阻塞写入,TLS和其他东西.但我们现在不关心它.)
首先,我们需要打开一个管道:
use autodie 'open'; open my $fh,'<','/path/to/pipe';
然后,我们用AnyEvent :: Handle包装它.创建Handle对象后,我们将它用于此管道上的所有操作.你可以完全忘记$fh,AnyEvent :: Handle会直接触摸它.
my $h = AnyEvent::Handle->new( fh => $fh );
现在我们可以使用$h来读取管道中可用的行:
$h->push_read( line => sub { my ($h,$line,$eol) = @_; say "Got a line: $line"; });
当下一行可用时,这将调用打印“获得一条线”的回调.如果要继续读取行,则需要使函数将自身推回到读取队列,如:
my $handle_line; $handle_line = sub { my ($h,$eol) = @_; say "Got a line: $line"; $h->push_read( line => $handle_line ); }; $h->push_read( line => $handle_line );
这将读取行并为每行调用$handle_line->(),直到文件关闭.如果你想提前停止阅读,这很容易……在这种情况下再也不要再推了一下. (您不必在行级读取;您可以要求在任何字节可用时调用您的回调.但这更复杂并留给读者练习.)
所以现在我们可以将它们组合成一个处理读取管道的守护进程.我们想要做的是:为行创建一个处理程序,打开管道并处理这些行,最后设置一个信号处理程序来干净地退出程序.我建议采用OO方法来解决这个问题;使每个动作(“处理来自访问日志文件的行”)具有启动和停止方法的类,实例化一堆动作,设置信号处理程序以干净地停止动作,启动所有动作,然后进入事件循环.这是很多与这个问题没有关系的代码,所以我们会做一些更简单的事情.但在设计程序时请记住这一点.
#!/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,$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;
现在你有一个程序从任意数量的管道中读取一行,打印在任何管道上接收的每一行(前缀为管道的路径),并在按下Control-C时完全退出!