Я рассмотрю часть вашего вопроса: как написать долгосрочную Perl-программу, которая работает с IO.
Самый эффективный способ написать программу на Perl, которая обрабатывает много одновременных операций ввода-вывода, - это использовать цикл обработки событий. Это позволит нам писать обработчики для событий, таких как «строка появилась в именованном канале» или «электронная почта была успешно отправлена» или «мы получили SIGINT». Важно, что это позволит нам составить произвольное количество этих обработчиков событий в одной программе. Это означает, что вы можете выполнять многозадачность, но при этом легко распределять состояние между задачами.
Мы будем использовать AnyEvent framework. Это позволяет нам писать обработчики событий, называемые наблюдателями, которые будут работать с любым циклом событий, поддерживаемым 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
, таймер будет отменен, и обратный вызов никогда не будет вызван.
О обратных вызовах, это sub { ... }
после cb =>
, и именно так мы обрабатываем события. Когда происходит событие, вызывается обратный вызов. Мы делаем свое дело, возвращаемся, и цикл обработки событий продолжает вызывать другие обратные вызовы по мере необходимости. Вы можете делать все, что захотите в обратных вызовах, включая отмену и создание других наблюдателей. Только не делайте блокирующий вызов, как system("/bin/sh long running process")
или my $line = <$fh>
или sleep 10
. Все, что блокирует, должно быть сделано наблюдателем; в противном случае цикл обработки событий не сможет запускать другие обработчики в ожидании завершения этой задачи.
Теперь, когда у нас есть таймер, нам просто нужно войти в цикл обработки событий. Как правило, вы выбираете цикл обработки событий, который хотите использовать, и вводите его особым образом, описанным в документации цикла обработки событий. EV хороший, и вы вводите его, звоня EV::loop()
. Но мы позволим AnyEvent принять решение о том, какой цикл событий использовать, написав AnyEvent->condvar->recv
. Не волнуйтесь, что это делает; это идиома, которая означает «войти в цикл обработки событий и никогда не возвращаться». (Вы увидите много о условных переменных или condvars, когда будете читать о AnyEvent. Они хороши для примеров в документации и в модульных тестах, но вы действительно не хотите использовать их в своей программе. Если вы Вы используете их в файле .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";
});
Это вызовет обратный вызов, который напечатает «Got a line», когда станет доступной следующая строка.Если вы хотите продолжить чтение строк, то вам нужно заставить функцию вернуться в очередь чтения, например:
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 );
Это будет читать строки и вызывать $handle_line->()
для каждой строки, пока файл не будетзакрыто.Если вы хотите прекратить читать рано, это легко ... просто не повторяйте push_read
в этом случае.(Вам не нужно читать на уровне строки; вы можете попросить, чтобы ваш обратный вызов вызывался всякий раз, когда становятся доступны любые байты. Но это сложнее и оставлено читателю в качестве упражнения.)
Так что теперь мыможет связать все это вместе в демон, который обрабатывает чтение каналов.Что мы хотим сделать: создать обработчик для линий, открыть каналы и обработать линии, и, наконец, настроить обработчик сигналов для чистого выхода из программы.Я рекомендую использовать ОО подход к этой проблеме;сделать каждое действие («обрабатывать строки из файла журнала доступа») классом с методами start
и stop
, создать экземпляр группы действий, настроить обработчик сигнала для чистой остановки действий, запустить все действия, а затемвойти в цикл событий.Это много кода, который не имеет отношения к этой проблеме, поэтому мы сделаем что-нибудь попроще.Но имейте это в виду при разработке вашей программы.
#!/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;
Теперь у вас есть программа, которая читает строку из произвольного числа каналов, печатает каждую строку, полученную в любом канале (с префиксом пути ктруба), и выходит чисто, когда вы нажимаете Control-C!