Основной цикл
8-13 Так же, как в udpclient, читаем строки из стандартного ввода, отправляем их удаленному хосту, читаем от него ответ и записываем его на стандартный вывод.
Хотя знакомые сетевые функции иногда принимают несколько иные аргументы и могут возвращать результат непривычным образом, но, в общем, программа в листинге 3.36 кажется знакомой и понятной. Тот, кто знаком с основами сетевого программирования и хотя бы чуть-чуть разбирается в Perl, может добиться высокой производительности.
Для сравнения в листинге 3.37 представлен TCP-сервер эхо-контроля. Вы можете соединиться с этим сервером с помощью программы telnet или любого другого TCP-приложения, способного вести себя как клиент эхо-сервера.
Здесь также видна знакомая последовательность обращений к API сокетов и, даже не зная языка Perl, можно проследить за ходом выполнения программы. Следует отметить две особенности, присущие Perl:
- вызов accept на строке 11 возвращает TRUE, если все хорошо, а новый со-кет возвращается во втором параметре (S1). В результате естественно выглядит цикл for, в котором принимаются соединения;
- поскольку recv возвращает адрес отправителя (или специальное значение undef), а не число прочитанных байт, получая длину строки $line (строка 16), следует явно проверять, не пришел ли признак конца файла. Оператор last выполняет те же действия, что break в языке С.
- TCL/Expect;
- Python;
- JavaScript;
- Visual Basic (для Windows).
Листинг 3.37. Версия эхо-сервера на языке Perl
pechos
1 #! /usr/bin/perl5
2 use Socket;
3 $port = shift;
4 $port = getservbyname( $port, 'tcp' ) if $port =~ /\D/;
5 die "Invalid port" unless $port;
6 socket( S, PF_INET, SOCK_STREAM, 0 ) die "socket: $!";
7 setsockopt( S,SOL_SOCKET, SO_REUSEADDR, pack( '1' , 1 ) )
8 die "setsockopt: $!";
9 bindf S, sockaddr_in( $port, INADDR_ANY ) ) die "bind: $!"
10 listen ( S, SOMAXCONN );
11 for( ; accept( SI, S ); close( SI ) )
12 {
13 while ( TRUE )
14 {
15 definedf recv( SI, $line, 120, 0 ) ) die "recv: $!"
16 last if length( $line ) == 0;
17 definedt send( SI, $line, 0 ) ) II die "send: $!";
18 }
19 }
Как видно из этих двух примеров, языки сценариев вообще и Perl в частности -это отличный инструмент для написания небольших тестовых программ, создания прототипов более крупных систем и утилит. Perl и другие языки сценариев активно применяются при разработке Web-серверов и специализированных Web-клиентов. Примеры рассматриваются в книгах [Castro 1998] и [Patchett and Wright 1998].
Помимо простоты и скорости разработки прототипа, есть и другие причины для использования языков сценариев. Одна из них - наличие в таких языках специальных возможностей. Например, Perl обладает прекрасными средствами для манипулирования данными и работы с регулярными выражениями. Поэтому во многих случаях Perl оказывается удобнее таких традиционных языков, как С.
Предположим, что каждое утро вам надо проверять, не появились ли в конференции comp.protocols.tcp-ip новые сообщения о протоколах TCP и UDP. В листинге 3.38 приведен каркас Peri-сценария для автоматизации решения этой задачи. В таком виде сценарий не очень полезен, так как он показывает все сообщения от сервера новостей, даже старые; отбор сообщений осуществляется довольно грубо. Можно было бы без труда модифицировать сценарий, ужесточив критерий отбора, но лучше оставить его таким, как есть, чтобы не запутаться в деталях языка Perl. Подробнее протокол передачи сетевых новостей (NNTP) рассматривается в RFC 977 [Каntor and Lapsley 1986].
Листинг 3.38. Peri-сценарий для формирования дайджеста из сетевых конференций
tcpnews
1 #' /usr/bin/perl5
2 use Socket;
3 $host = inet_aton( 'nntp.ix.netcom.com') die "хост: $!";
4 $port = getservbyname('nntp1, 'tcp') die "некорректный порт";
5 socket( S, PF_INET, SOCK_STREAM, 0 ) die "socket: $!";
6 connect! S, sockaddr_in( $port, $host ) ) die "connect: $!";
7 select( S ) ;
8 $1 = 1;
9 select( STDOUT );
10 print S "group сотр.protocols.tcp-ip\r\n";
11 while ( $line = <S> )
12 {
13 last if $line =~ /^211/;
14 }
15 ($rc, $total, $start, $end ) = split( /\s/, $line );
16 print S "xover $start-$end\nguit\r\n" ;
17 while ( $line = <S> )
18 {
19 ( $no, $sub, $auth, $date ) = split( /\t/, $line );
20 print "$no, $sub, $date\n" if $sub =~ /TCPIUDP/;
21 }
22 close( S );
Инициализация и соединение с сервером новостей
2- 6 Это написанный на Perl аналог логики инициализации стандартного TCP-клиента.
Установить режим небуферизованного ввода/вывода
7-9 В Perl функция print вызывает стандартную библиотеку ввода/вывода, а та, как упоминалось в совете 17, буферизует вывод в сокет. Эти три строки отключают буферизацию. Хотя по виду оператор select напоминает системный вызов select, который рассматривался ранее, в действительности он просто указывает, какой файловый дескриптор будет использоваться по умолчанию. Выбрав дескриптор, вы можете отменить буферизацию вывода в сокет S, задав ненулевое значение специальной переменной $ |, используемой в Perl.
Примечание: Строго говоря, это не совсем так. Эти действия приводят к тому, что после каждого вызова wri te или print для данного дескриптора автоматически выполняется функция fflush. Но результат оказывается таким же, как если бы вывод в сокет был не буферизован.
В строке 9 stdout восстанавливается как дескриптор по умолчанию.
Выбрать группу comp.protocols. tcp-ip
10-14 Посылаем серверу новостей команду group, которая означает, что текущей группой следует сделать comp. protocols. tcp-ip. Сервер отвечает строкой вида
211 total_articles first_article# last_article# group_namespace
В строке 13 вы ищете именно такой ответ, отбрасывая все строки, которые начинаются не с кода ответа 211. Обратите внимание, что оператор <. . . > сам разбивает на строки входной поток, поступающий от TCP.
15-16 Обнаружив ответ на команду group, нужно послать серверу строки
xover first_article#-last_article#
quit
Команда xover запрашивает сервер, заголовки всех статей с номерами из заданного диапазона. Заголовок содержит список данных, разделенных символами табуляции: номер статьи, тема, автор, дата и время, идентификатор сообщения, идентификаторы сообщений для статей, на которую ссылается данная, число баи тов и число строк. Команда quit приказывает серверу разорвать соединение, та как запросов больше не будет.
Отбор заголовков статей
17-20 Читаем каждый заголовок, выделяем из него интересующие нас поля и оставляем только те заголовки, для которых в теме присутствует строка «TCP» или «UDP».
Запуск tcpnews дает следующий результат:
bsd: $ tcpnews
74179, Re: UDP multicast, Thu, 22 Jul 1999 21:06:47 GMT
74181, Re: UDP multicast, Thu, 22 Jul 1999 21:10:45 -0500
74187, Re: UDP multicast, Thu, 22 Jul 1999 23:23:00 +0200
74202, Re: NT 4.0 Server and TCP/IP, Fri, 23 Jul 1999 11:56:07 GMT
74227, New Seiko TCP/IP Chip, Thu, 22 Jul 1999 08:39:09 -0500
74267, WATTCP problems, Mon, 26 Jul 1999 13:18:14 -0500
74277, Re: New Seiko TCP/IP Chip, Thu, 26 Jul 1999 23:33:42 GMT
74305, TCP Petri Net model, Wed, 28 Jul 1999 02:27:20 +0200
bsd: $
Помимо языка Perl, есть и другие языки сценариев, пригодные для сетевого программирования, например:
Их можно использовать для автоматизации решения простых сетевых задач, построения прототипов и быстрого создания удобных утилит или тестовых примеров. Как вы видели, языки сценариев часто проще применять, чем традиционные компилируемые языки программирования, поскольку интерпретатор берет на себя многие технические детали (конечно, расплачиваясь эффективностью). Усилия, потраченные на овладение хотя бы одним из таких языков, окупятся ростом производительности труда.