AnyEvent и AnyEvent::Run на примере пинга нескольких устройств

Потребовалось пинговать несколько устройств в одной подсети. Эту задачу можно сделать в лоб, но мне захотелось через событийное программирование и модуль AnyEvent.

Идея следующая: запустить N процессов утилиты ping и через специальный метод обратного вызова получать строку вывода утилиты и выводить на экран.

Для асинхронной работы процессами был взят модуль AnyEvent::Run, как собрать deb пакет из cpan, можно почитать в журнале Pragmatic Perl(Сборка deb-пакетов модулей Perl для Debian и Ubuntu)

 

#!/usr/bin/perl

use 5.010;
use strict;
use warnings;

use AnyEvent;
use AnyEvent::Run;

my @IPS = qw (
  ya.ru
  google.com
  mail.ru
);

my @HANDLERS;

my $cv = AnyEvent->condvar;

foreach my $ip (@IPS)
{
  my $handle = AnyEvent::Run->new(
    cmd      => [ 'ping', $ip, '-i 5' ],
    priority => 19,
    on_read  => sub {
      my $handler = shift;
      $handler->push_read(
        line => sub {
          my ( $hdl, $line ) = @_;
          say $line;
        }
      );
    }
  );

  push @HANDLERS, $handle;
}

$cv->recv;

on_read обратный вызов из AnyEvent::Handle который вызывается кода данные готовы для чтения, а очередь запросов на чтение пуста. push_read из того же AnyEvent::Handle добавляет обратный вызов на чтение данных, и определяет сколько необходимо данных для чтения. В данном случае устанавливается срабатывание на чтение одной строки.

Если запустить, то можно увидеть такую картину:

PING google.com (213.158.11.226) 56(84) bytes of data.
64 bytes from cache.google.com (213.158.11.226): icmp_seq=1 ttl=61 time=79.9 ms
64 bytes from cache.google.com (213.158.11.226): icmp_seq=2 ttl=61 time=49.4 ms
PING mail.ru (217.69.139.202) 56(84) bytes of data.
64 bytes from mail.ru (217.69.139.202): icmp_seq=1 ttl=55 time=90.7 ms
PING ya.ru (213.180.193.3) 56(84) bytes of data.
64 bytes from www.yandex.ru (213.180.193.3): icmp_seq=1 ttl=56 time=93.9 ms
64 bytes from mail.ru (217.69.139.202): icmp_seq=2 ttl=55 time=82.2 ms
64 bytes from www.yandex.ru (213.180.193.3): icmp_seq=2 ttl=56 time=106 ms
64 bytes from cache.google.com (213.158.11.226): icmp_seq=3 ttl=61 time=140 ms
64 bytes from mail.ru (217.69.139.202): icmp_seq=3 ttl=55 time=98.7 ms
64 bytes from www.yandex.ru (213.180.193.3): icmp_seq=3 ttl=56 time=97.5 ms
64 bytes from cache.google.com (213.158.11.226): icmp_seq=4 ttl=61 time=98.4 ms
64 bytes from mail.ru (217.69.139.202): icmp_seq=4 ttl=55 time=126 ms
64 bytes from www.yandex.ru (213.180.193.3): icmp_seq=4 ttl=56 time=151 ms
64 bytes from cache.google.com (213.158.11.226): icmp_seq=5 ttl=61 time=112 ms

Дампим Telnet/CLI сессию, для последующего анализа

Продолжая темы: пишем заглушку для Telnet/CLI, пишем заглушку для Telnet/CLI на Perl.

Студент попался нерадивый и не знает как сделать дамп/трассировку telnet сессии. Приведу два примера: средствами утилитами командной строки GNU/Linux, и средствами Perl.

Командная строка GNU/Linux

netcat host 23 | tee dump.dat

Тут мы использовали netcat и tee, netcat используем для получения сырых данных, включает управляющие символы telnet, tee утилита которая перенаправляет входящий поток данных в stdout и файл.

Средствами Perl

Тут мы пользуемся пакетом Net::Telnet в конструкторе которого можно указать логировать вход(Input_log), логировать выход(Option_log)

Пример:

#!/usr/bin/perl 

use 5.010;
use strict;
use warnings;
use Net::Telnet;

my $session = new Net::Telnet (Host=>"host", Input_log=>"input_dump.dat");
$session->cmd(...);
$session->close;

Пишем заглушку для Telnet/CLI на Perl

В Пишем заглушку для Telnet/CLI в качестве сервера выступал tcpserver из пакета ucspi-tcp. Раз уж использовал Perl, то приведу пример как можно сделать на чистом Perl.

Perl богат модулями на все случаи жизни, среди его модулей есть пакет Net::Server который представляет из себя движок для написание сервера. В основе Net::Server лежит паттерн шаблонный метод, он берет на себя работу по созданию и настройке сокета, ожидании и обработки нового подключения, и выводит для использования ряд переопределяемых методов.

Стоит дополнительно отметить, что после создания нового соединения, Net::Server переключает дескриптор сокета, на дескрипторы stdin и stdout, делая возможность читать из сокета привычным <>, а писать привычным print или say.

#!/usr/bin/perl

package MockServer;

use 5.010;
use strict;
use warnings;
use base qw(Net::Server::PreFork);

sub process_request
{
    while(<>)
    {
        chomp;
        if(/quit/)
        {
            say "bye";
            return;
        }

        print `$_`;
	}
}

1;

Net::Server::PreFork это реализация Net::Server использующая pre-fork модель обработки соединений, т.е. создается N процессов(ну, нет нормальной многопоточности в скриптовых языках Perl/Python/PHP) каждый из которых может держать по M соединений.

Реализация сервера:

#!/usr/bin/perl

use 5.010;
use MockServer;

my $server = MockServer->new(port=>23023);
$server->run();

Пишем заглушку для Telnet/CLI.

Сейчас работаю на контору, которая занимается мониторингом сетей передачи данных и сетевого оборудования. Одному из студентов на полставки было получено разработать компонент, получающий характеристики с одного устройства по CLI(Command-line interface) через Telnet. Все вроде как шло хорошо, пока не дошло дело до тестирования.

Студенту сразу же захотелось протестировать свой код на боевом сервере, видите ли ему нужна реальная железка. Естественно он был послан в песочницу, где он должен сделать заглушку.
Естественно я не могу требовать от других то, что не могу сделать сам. Привожу пример такой заглушки без всяких сокетов!
Будем использовать мощь UNIX-way в GNU/Linux.

Есть такой крутой man Daniel J. Bernstein, который помимо всего прочего написал пакет ucspi-tcp, это набор утилит с интерфейсом командной строки, для разработки клиент-серверных приложений.

Чувствуете мощь *nix систем ? Клиент-серверное приложение на bash’e !

Будем использовать программу tcpserver, а вместо баша любимый Perl, вот так выглядит её интерфейс:

tcpserver opts host port prog

opts опции запуска, host хост на котором будет висеть сервер, port порт на котором будет висеть сервер, prog наш скрипт или программа.

Как это все работает? tcpserver вешается на выбранный хост и порт, и начинает принимать входящие соединения. Когда кто-то к нему подключается, то он переключает дескриптор сокета на дескриптор 0 для чтения, и дескриптор 1 для записи и запускает программу prog. Для тех, кто не в курсе:

  • 0 — это дескриптор стандартного входа(stdin);
  • 1 — это дескриптор стандартного выхода(stdout).

Нам остается только лишь написать скрипт/программу которая читает строку их stdin, в зависимости от команды генерируем ответ в том виде, в каком отвечает железяка. В качестве примера привожу простой скрипт на Perl, считывает команду, если это quit, то выходим, иначе запускает команду и выводим результат на экран:

use 5.010;
 
$|=1;
 
while(<>)
{
    chomp;
    if(/quit/)
    {
        say "bye";
        exit(0);
    }
    
    print `$_`;
}

Запуск сервера:

tcpserver localhost 23023 ./cli.pl

Проверяем, подключаемся по telnet и получаем информацию о процессоре:

$ telnet localhost 23023 
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
cat /proc/cpuinfo