Вернемся к программе, приведенной ранее и снова рассмотрим подпрограмму interact():
sub interact { my $sock = shift; STDIN->fdopen ($sock, "<") or die "Can’t. reopen STDIN: $!"; STDOUT->fdopen($sock,">") or die "Can’t reopen STDOUT: $!"; STDERR->fdopen($sock,"> ") or die "Can’t reopen STDERR: $!"; $|=1; my $bot = Chatbot::Eliza->new; $bot->command_interface(); }
В этом психотерапевтическом демоне используется достаточно универсальный подход к обработке входящих соединений и ветвлению, которое сможет прогрессивно обрабатывать запросы, например при разработке билинговой системы IP-телефонии . В действительности, подпрограмма interact () — это единственное место, в котором находится код, относящийся к конкретному приложению.
А теперь рассмотрим следующую версию подпрограммы interact():
sub interact { my $sock = shift; STDIN->fdopen($sock, or die "Can’t reopen STDIN: $!"; STDOUT->fdopen($sock, or die "Can’t reopen STDOUT: $!"; STDERR->fdopen($sock, or die "Can1t reopen STDERR: $!" exec "eliza.pl"; }
После переоткрытия устройств STDIN, STDOUT и STDERR в сокет просто выполняется с помощью функции ехес () первоначальный сценарий командной строки eliza.pl. При условии, что этот сценарий находится в пути доступа командного интерпретатора, интерпретатор Perl запустит его и заменит текущий процесс новым. Будет выполняться версия сценария eliza.pl с интерфейсом командной строки, которая предусматривает чтение данных, введенных пользователем, с устройства STDIN и отправку ответов психотерапевта на устройство вывода STDOUT. Однако устройства STDIN, STDOUT и STDERR унаследованы от родительского процесса, поэтому программа будет фактически считывать из сокета и записывать в него. Мы превратили программу с интерфейсом командной строки в серверное приложение, не изменив ни одной строки исходного кода!
В действительности, можно сделать подпрограмму interact () еще более универсальной, предусмотрев для нее параметры, которые содержат имя и параметры командной строки выполняемой команды.
sub interact { my ($sock, @command) = @_; STDIN->fdopen($sock,"<") or die "Can’t reopen STDIN: $!"; STDOUT->fdopen ($sock, ">" or die "Can’t reopen STDOUT: $!"; STDERR->fdopen ($sock, ">" or die "Can’t reopen STDERR: $!"; exec @command; }
Теперь в качестве сервера можно применять любую программу, которая читает
устройства STDIN и пишет на устройство STDOUT. Например, в системах UNIX для простого эхо-сервера достаточно передать подпрограмме interact () вместо параметра путь к программе /bin/cat. Поскольку команда cat считывает с устройства STDIN и записывает их без изменений на устройство STDOUT, она может отправлять другому участнику соединения все, что считано из сокета.
Этот простой способ создания сетевых серверов не остался незамеченным проектировщиками операционной системы. В системах UNIX (и Linux) есть стандартный демон inetd, который представляет собой немного доработанную и настраиваемую версию этого универсального сервера, позволяющую запускать и эксплуатировать ряд сетевых служб по требованию.
Демон inetd запускается во время начальной загрузки. Он считывает файл конфигурации /etc/inetd.conf, который, по сути, представляет собой список контролируемых портов и программ, связанных с каждым портом. При поступлении запроса на соединение в любой из контролируемых портов демон inetd вызывает функцию accept () для получения нового подключенного сокета, выполняет ветвление, перенаправляет три стандартных дескриптора файлов в сокет и, наконец, запускает соответствующую программу.
Преимуществом этой системы является то, что вместо запуска (вручную или во время начальной загрузки) десятков служб, применяемых лишь время от времени, inetd запускает их только по мере необходимости. Еще одной особенностью демона inetd является то, что его конфигурацию можно изменять динамически, направляя ему сигнал HUP. При получении такого сигнала он повторно считывает свой файл конфигурации и изменяет настройку, если в этом есть необходимость. Это позволяет вводить новые службы без перезагрузки компьютера.