Изучаем Perl

       

Сетевые клиенты


Немногие компьютеры (и, соответственно, работающие на них пользователи) остаются в изоляции от остального компьютерного мира. Сети, когда-то бывшие достоянием в основном государственных научно-исследовательских лабораторий и факультетов вычислительной техники крупнейших университетов, сейчас доступны практически каждому — даже пользователю домашнего компьютера с модемом, устанавливающему по коммутируемой линии соединение с провайдером с помощью протоколов SLIP или РРР. Сейчас сети больше чем когда-либо используются в повседневной работе организациями и пользователями всех слоев общества — для обмена электронной почтой, планирования встреч, управления распределенными базами данных, доступа к информации предприятий, получения прогнозов погоды, чтения текущих новостей, разговоров с собеседниками из другого полушария, рекламирования продуктов и фирм через Web и т.д.

У всех этих приложений есть одна общая черта: они работают на основе TCP, фундаментального протокола, который обеспечивает взаимодействие между собой всех сетей, входящих в Internet*. И мы имеем в виду не только Internet. He считая брандмауэров, базовая технология везде одна, независимо от того, устанавливается соединение по Internet, соединение между офисами компании или соединение между кухней и подвалом вашего дома. Это удобно: для того чтобы ориентироваться во всех применениях Internet/intra-net, вы должны изучить только одну технологию.

Как же с помощью сети позволить приложению, работающему на одной машине, общаться с другим приложением, которое может функционировать на совершенно другой машине? Средствами Perl это сделать очень легко, но сначала вам, наверное, нужно немного узнать о том, как работает сеть на базе протокола TCP.

* На самом деле коммуникации в Internet обеспечиваются протоколом IP (Internet Protocol), а протокол TCP (Transmition Control Protocol) является протоколом более высокого уровня.

Даже если вы еще ни разу не работали в компьютерной сети, вы уже знаете о системе с установлением соединений: это — телефонная сеть. И пусть вас не смущают причудливые словосочетания вроде "программирование систем клиент/сервер". Видя слово "клиент", читайте "вызывающий абонент", а видя слово "сервер" — читайте "отвечающий абонент".


Звоня кому- то по телефону, вы выступаете в роли клиента. Тот, кто поднимает трубку на другом конце линии, является сервером.

Программисты, имеющие опыт работы на С, возможно, знакомы с гнездами (sockets) . Гнездо — это интерфейс к сети в том же самом смысле, что и дескриптор файла — это интерфейс к файлам в файловой системе. В частности, для тех простых программ, которые мы продемонстрируем ниже, вы можете пользоваться дескриптором гнезда так же, как дескриптором файла*.

Вы можете читать данные из гнезда, записывать в него данные, а также выполнять обе эти операции. Это объясняется тем, что гнездо — особый вид двунаправленного дескриптора файла, представляющего сетевое соединение. В отличие от обычных файлов, созданных посредством функции open, гнезда создаются с помощью низкоуровневой функции socket.

Давайте выжмем еще немного из нашей телефонной модели. Звоня на коммутатор большой компании, вы можете попросить соединить вас с конкретным отделом по названию (например, с отделом кадров) или по номеру (например, "дополнительный 213"). Представьте, что каждый сервис, работающий на компьютере,— это отдел большой корпорации. Иногда сервис имеет несколько имен, например http и www, но только один номер, например 80. Этот номер, связанный с именем сервиса, называется его портом. С помощью Perl-функций getservbyname иgetservbyport МОЖНО найти имя сервиса по номеру его порта и наоборот. Вот некоторые стандартные ТСР-сервисы и номера их портов:



Сервис
Порт Назначение
echo 7 Принимает все вводимые данные и воспроизводит их
discard 9 Принимает все, но ничего не возвращает
daytime 13 Возвращает текущую дату и местное время
ftp 21 Сервер для обработки запросов пересылки файлов
telnet 23 Сервер для интерактивных telnet-сеансов
smtp 25 Простой протокол пересылки почты; демон-почтальон
time 37 Возвращает число секунд, прошедших с начала 1900-го года (в двоичном формате)
http 80 Сервер World Wide Web
nntp 119 Сервер телеконференций
<


* Почти так же; поиск по гнезду невозможен.

Хотя гнезда изначально разрабатывались для Berkeley UNIX, все возрастающая популярность Internet побудила практически всех поставщиков операционных систем включить в свои продукты поддержку гнезд для программирования систем клиент/сервер. Функция socket является относительно низкоуровневой, а мы в нашей книге рассматриваем в основном высокоуровневые средства Perl. Рекомендуем вам пользоваться более удобным модулем IO::Socket*, который мы будем применять во всех наших примерах. Это значит, что мы также будем применять некоторые объектно-ориентированные конструкции Perl. Краткое введение в эти конструкции дано в главе 19. Более подробное введение в объектно-ориентированное программирование на Perl приведено на man-странице perltoot(l) и в главе 5 книги Programming Perl.

Подробное рассмотрение TCP/IP выходит за рамки нашей книги, но мы можем представить хотя бы несколько простых клиентов. О серверах, которые более сложны, вы можете узнать в главе 6 книги Programming Perl и на man-странице perlipc(l).



Простой клиент



Для нашего простейшего клиента мы выберем довольно скучный сервис, который называется daytime ("время суток"). Сервер времени суток посылает установившему соединение клиенту одну строку данных, содержащую значение времени суток на этом удаленном сервере, а затем закрывает соединение.

Вот клиент:

#!/usr/bin/peri -w use 10::Socket;

$remote = 10::Socket::INET->new(

Proto => "tcp",

PeerAddr => "localhost",

PeerPort => "daytime(13)",

) or die "cannot connect to daytime port at localhost";

while ( <$remote> ) ( print )

Запустив эту программу, вы должны получить с сервера примерно следующее:

Thu May 8 11:57:15 1997

* IO::Socket входит в состав стандартного дистрибутива Perl версии 5.004. Если у вас более ранняя версия, получите этот модуль из CPAN, где вы найдете модули с простыми интерфейсами к следующим сервисам: DNS, ftp, Ident(RFC 931), NIS и NISPlus, NNTP, ping, POP3, SMTP, SNMP, SSLeay, telnet, time и др.



Вот что означают параметры конструктора new:

Proto

Протокол, который следует использовать. В данном случае возвращенный дескриптор гнезда будет подключен к TCP-гнезду, потому что нам нужно потоковое соединение, т.е. соединение, которое работает почти так же, как старый добрый файл. Не все гнезда такие. Например, с помощью протокола UDP можно создать дейтаграммное гнездо, используемое для передачи сообщений.

PeerAddr

Имя или Internet-адрес удаленного хоста, на котором работает сервер. Мы могли бы указать имя подлиннее, например www.perl.com, или адрес вроде 204.148.40.9. Однако для демонстрационных целей мы использовали специальное хост-имя localhost, которое всегда должно обозначать машину, на которой вы работаете. Имени localhost соответствует Internet-адрес 727.0.0.1.

PeerPort

Имя или номер порта сервиса, с которым мы хотим соединиться. В системах с нормально конфигурированным системным файлом серви-сов* мы могли бы обойтись просто именем daytime, но на всякий случай мы все же дали в круглых скобках номер порта (13). Использование одного номера без имени тоже сработало бы, но осторожные программисты стараются не использовать числа вместо констант.

Вы обратили внимание на то, как возвращаемое значение конструктора new используется в роли дескриптора файла в цикле while? Это то, что называется косвенным дескриптором файла — скалярная переменная, содержащая дескриптор файла. Его можно использовать точно так же, как обычный дескриптор. Например, так из него можно прочитать одну строку:

$line = <$handle>;

так — все остальные строки:

@lines = <$handle>;

а так — послать в него строку данных:

print $handle "some data\n";

* Системный файл сервисов в UNIX находится в каталоге /etc/services.



Клиент webget



Вот простой клиент, который устанавливает соединение с удаленным сервером и получает с него список документов. Этот клиент интереснее предыдущего, потому что перед получением ответа сервера он посылает на него строку данных.

#!/usr/bin/peri -w use 10::Socket;



unless (@ARGV > 1) ( die "usage: $0 host document ..." } $host = shift (OARGV);

foreach $document ( OARGV ) (

$remote == 10::Socket::INET->new( Proto => "tcp",

PeerAddr => $host,

PeerPort => "http (80)",

);

unless ($remote) ( die " cannot connect to http daemon on $host" )

$remote->autoflush(l) ;

print $remote "GET $document HTTP/I.0\n\n";

while ( <$remote> ) ( print )

-close $remote;

)

Подразумевается, что Web-сервер, на котором работает сервис http, использует свой стандартный порт (номер 80). Если сервер, с которым вы пытаетесь установить соединение, использует другой порт (скажем, 8080), то в качестве третьего аргумента конструктора new () нужно указать PeerPort => 8080. При работе с этим гнездом применяется метод autoflush, потому что в противном случае система буферизировала бы выходную информацию, которую мы ей передали. (Если у вас компьютер Macintosh, то нужно заменить все \п в коде, предназначенном для передачи данные по сети, на

\015\012.)

Соединение с сервером — это лишь первый этап процесса: установив соединение, вы должны начать говорить на языке этого сервера. Каждый сервер сети использует свой собственный маленький командный язык, и входные данные, подаваемые на сервер, должны быть сформулированы именно на этом языке. Начинающаяся словом GET строка, которую мы послали серверу, соответствует синтаксису протокола HTTP. В данном случае мы просто запрашиваем каждый из указанных документов. Да, мы действительно создаем новое соединение для каждого документа, несмотря на то, что это тот же самый хост. Именно так функционирует НТТР-серевер. (Последние версии Web-броузеров могут требовать, чтобы удаленный сервер оставлял соединение открытым на некоторое время, но сервер не обязан удовлетворять такой запрос.)

Мы назовем нашу программу webget.

Вот как ее можно было бы выполнить:

shell prompt? webget www.peri.com /guanaco.html

HTTP/I.I 404 File Not Found

Date: Thu, 08 May 1997 18:02:32 GMT



Server: Apache/1.2b6

Connection: close

Content-type: text/html

<HEADXTITLE>404 File Not Found</TITLEX/HEAD>

<BODYXHl>File Not Found </H1>

The request URL /guanaco. html was not found on this server.

<P>


</BODY>

Это, конечно, не очень интересно, потому что программа не нашла конкретный документ, однако длинный ответ не поместился бы на этой странице.

Чтобы ознакомиться с более развитой версией данной программы, вам нужно найти программу Iwp-request,

входящую в состав модулей LWP из CPAN. (LWP мы вкратце рассмотрели в конце главы 19.)



Интерактивный клиент



Создать программу-клиент, которая просто читает все с сервера или посылает одну команду, получает один ответ, а затем завершает свою работу, очень легко. А как насчет создания чего-нибудь полностью интерактивного, вроде telnefi

Мы имеем в виду приложение, которое позволяло бы вам набрать строку, получить ответ, набрать еще одну строку, вновь получить ответ и т.д. (В принципе, telnet

обычно работает в символьном, а не в строковом режиме, но идею вы поняли.)

Этот клиент — более сложный, чем те два, с которыми мы имели дело до сих пор, но если вы работаете в системе, которая поддерживает мощный вызов fork, решение получится не слишком сложным. Установив соединение с тем сервисом, с которым вы хотите пообщаться, клонируйте свой процесс вызовом fork. Каждый из созданных идентичных процессов должен выполнить очень простое задание: родительский копирует все из гнезда на стандартный вывод, а порожденный одновременно копирует все со стандартного ввода в гнездо. Реализовать это с помощью только одного процесса было бы гораздо труднее, потому что легче написать два процесса для выполнения одной задачи, чем один процесс — для выполнения двух задач*.

Вот наш код:

#!/usr/bin/peri -w use strict;

use 10::Socket;

my ($host, $port, $kidpid, $handle, $line);

unless (8ARGV == 2 ) ( die "usage: $0 host port" ) ($host, $port) = 8ARGV;

# создать tcp-соединение с указанным хостом и портом $handle - 10::Socket::INET->new(Proto => "tcp",



PeerAddr => $host,

PeerPort => $port)

or die "can't connect to port $port on $host: $!";

$handle->autoflush(l); # и результат сразу же попадает туда print STDERR "[Connected to $host:$port]\n";

# разбить программу на два процесса-близнеца

die "can't fork: $!" unless defined ($kidpid = fork());

# блок if{( выполняется только в родительском процессе if($kidpid) (

# копировать данные из гнезда на стандартный вывод while (defined ($line = <$handle> )) f print STDOUT $line;

1

kill ("TERM",$kidpid); # послать в порожденный процесс сигнал SIGTERM >

# блок else(} выполняется только в порожденном процессе else 1

# копировать данные со стандартного ввода в гнездо while (defined ($line = <STDIN>)) ( print $handle $line;

} 1

* Принцип сохранения простоты — один из краеугольных камней не только философии UNIX, но и высококачественного проектирования программного обеспечения. Наверное, именно поэтому он распространился и на другие системы.

Функция kill в блоке if родительского процесса пошлет сигнал в наш порожденный процесс (в текущий момент работающий в блоке else), как только удаленный сервер закроет свою сторону соединения.



Что еще почитать о сетях



О

сетях можно говорить и говорить, но мы дадим ссылки на источники, которые помогут вам перейти от разговоров к делу. В главе 6 книги Programming Perl и на man-странице perlipc(l) описано межпроцессное взаимодействие в общем; на man-странице IO::Socket(3) — объектная библиотека; на man-странице Socket(3) —

низкоуровневый интерфейс к гнездам. Более опытным программистам рекомендуем книгу Unix Network Programming (by Richard Stevens, Addison-Wesley), в которой очень хорошо освещены все вопросы данной темы. Будьте, однако, осторожны: большинство книг по программированию гнезд написаны С-программистами.

|     Назад    

|     Вперед    

|

| Содержание | Предисловие | Введение | Ссылки

| Глава 1 | Глава 2 | Глава 3 | Глава 4 | Глава 5 | Глава 6 | Глава 7 | Глава 8 | Глава 9 | Глава 10

| Глава 11 | Глава 12 | Глава 13 | Глава 14 | Глава 15 | Глава 16 | Глава 17 | Глава 18 | Глава 19

| Приложение А | Приложение Б | Приложение В | Приложение Г |


Содержание раздела