Эффективное программирование TCP-IP

       

Основной цикл


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 в языке С.
  • Листинг 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, есть и другие языки сценариев, пригодные для сетевого программирования, например:

  • TCL/Expect;


  • Python;


  • JavaScript;


  • Visual Basic (для Windows).


  • Их можно использовать для автоматизации решения простых сетевых задач, построения прототипов и быстрого создания удобных утилит или тестовых примеров. Как вы видели, языки сценариев часто проще применять, чем традицион­ные компилируемые языки программирования, поскольку интерпретатор берет на себя многие технические детали (конечно, расплачиваясь эффективностью). Усилия, потраченные на овладение хотя бы одним из таких языков, окупятся ростом производительности труда.


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