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

       

TCP-серверы


Для TCP-серверов inetd прослушивает хорошо известные порты, ожидая запроса на соединение, затем принимает соединение, ассоциирует с ним файловые Дескрипторы stdin, stdout и stderr, после чего запускает приложение. Таким образом, сервер может работать с соединением через дескрипторы 0, 1 и 2. Если это допускается конфигурационным файлом inetd (/etc/ inetd.conf), то inetd продолжает прослушивать тот же порт. Когда в этот порт поступает запрос на но­вое соединение, запускается новый экземпляр сервера, даже если первый еще не завершил сеанс. Это показано на рис. 3.2. Обратите внимание, что серверу не нужно обслуживать нескольких клиентов. Он просто выполняет запросы одного клиента, а потом завершается. Остальные клиенты обслуживаются дополнительными экземплярами сервера.

Рис. 3.2. Действия inetd при запуске TCP-сервера

Применение inetd освобождает от необходимости самостоятельно устанавливать TCP или UDP-соединение и позволяет писать сетевое приложение почти так же, как обычный фильтр. Простой, хотя и не очень интересный пример при­веден в листинге 3.3.

Листинг 3.3. Программа rlnumd для подсчета строк

1    #include <stdio.h>

2    void main( void )

3    {

4    int cnt = 0;

5    char line[ 1024 ];

6    /*

7      *Мы должны явно установить режим построчной буферизации,

8      *так как функции из   библиотеки  стандартного  ввода/вывод



9      *не считают сокет терминалом. */

10   setvbuf( stdout, NULL, _IOLBF, 0 );

11   while ( fgets ( line, sizeof( line ) , stdin ) != NULL )

12     printf( "%3i: %s", ++cnt, line );

13   }

По поводу этой программы стоит сделать несколько замечаний:

  • в тексте программы не упоминается ни о TCP, ни вообще о сети. Это не зна­чит, что нельзя выполнять связанные с сокетами вызовы (getpeername, [gs ] etsockopt и т.д.), просто в этом не всегда есть необходимость. Нет ника­ких ограничений и на использование read и write. Кроме того, можно пользоваться вызовами send, recv, sendto и recvfrom, как если бы inetd не было.

  • режим буферизации строк приходится устанавливать самостоятельно, по­скольку стандартная библиотека ввода/вывода автоматически устанавливает подобный режим только в том случае, если считает, что вывод производится на терминал. Это позволяет обеспечить быстрое время реакции для интерактивных приложений;


  • стандартная библиотека берет на себя разбиение входного потока на строки. Об этом уже говорилось в совете 6;


  • предполагаем, что не будет строк длиннее 1023 байт. Более длинные строки будут разбиты на несколько частей, и у каждой будет свой номер;


  • Примечание: Этот факт, который указан в книге [Oliver 2000], служит еще одним примером того, как можно легко допустить ошибку переполнения буфера. Подробнее этот вопрос обсуждался в совете 11.

  • хотя это приложение тривиально, но во многих «настоящих» TCP-приложениях, например telnet, rlogin и ftp, используется такая же техника.


  • Программа в листинге 3.3 может работать и как «нормальный» фильтр, и как Удаленный сервис подсчета строк. Чтобы превратить ее в удаленный сервис, нужно только выбрать номер порта, добавить в файл /etc/ services строку с именем сервиса и номером порта и включить в файл /etc/inetd.conf строку, описывающую этот сервис и путь к исполняемой программе. Например, если вы назовете сервис rlnum, исполняемую программу для него –

    rlnumd и назначите ему порт 8000, то надо будет добавить в /etc/services строку

    rlnum 8000/tcp # удаленный сервис подсчета строк,

    а в /etc/inetd.conf - строку

    rlnum stream tcp nowait jcs /usr/home/jcs/rlnumd rlnumd.

    Добавленная в /etc/services строка означает, что сервис rlnum использует протокол TCP по порту 8000. Смысл же полей в строке, добавленной в /etc/inetd.conf, таков:

  • имя сервиса, как он назван в /etc/services. Это имя хорошо известного порта, к которому подсоединяются клиенты данного сервера. В вашем примере - rlnum;


  • тип сокета, который нужен серверу. Для TCP-серверов это stream, a для UDP-серверов - dgram. Поскольку здесь сервер пользуется протоколом ТCP указан stream;




  • протокол, применяемый с сервером, - tcp или udp. В данном примере это tср;


  • флаг wait/nowait. Для UDP- серверов его значение всегда wait, а для ТСР-серверов - почти всегда nowait. Если задан флаг nowait, то inetd сразу после запуска сервера возобновляет прослушивание связанного с ним хоро­шо известного порта. Если же задан флаг wait, то inetd не производит ника­кой работы с этим сокетом, пока сервер не завершится. А затем он возобновляет прослушивание порта в ожидании запросов на новые соединения (для stream-серверов) или новых датаграмм (для dgram-серверов). Если для stream-серве­ра задан флаг wait, то inetd не вызывает accept для соединения, а переда­ет сокет, находящийся в режиме прослушивания, самому серверу, который должен принять хотя бы одно соединение перед завершением. Как отмечено в сообщении [Kacker 1998], задание флага wait для TCP-приложения - это мощная, но редко используемая возможность. Здесь приводится несколько применений флага wait для TCP-соединений:


  • - в качестве механизма рестарта для ненадежных сетевых программ-де­монов. Пока демон работает корректно, он принимает соединения от клиентов, но если по какой-то причине демон «падает», то при следую­щей попытке соединения inetd его рестартует;

    -         как способ гарантировать одновременное подключение только одного клиента;

    -         как способ управления многопоточным или многопроцессным приложе­нием, зависящим от нагрузки. В этом случае начальный процесс запус­кается inetd, а затем он динамически балансирует нагрузку, создавая по мере необходимости дополнительные процессы или потоки. При уменьшении нагрузки, потоки уничтожаются, а в случае длительного простоя завершает работу и сам процесс, освобождая ресурсы и возвра­щая прослушивающий сокет inetd.

    В данном примере задан флаг nowait, как и обычно для TCP-серверов.

    • имя пользователя, с правами которого будет запущен сервер. Это имя долж­но присутствовать в файле /etc/passwd. Большинство стандартных серве­ров, прописанных в inetd. conf, запускаются от имени root, но это совер­шенно необязательно. Здесь в качестве имени пользователя выбрано jcs;


    • полный путь к файлу исполняемой программы. Поскольку rlnumd находит­ся в каталоге пользователя jcs, задан путь /usr/home/ jcs/rlnumd;


    • до пяти аргументов (начиная с argv [ 0 ]), которые будут переданы серверу. По­скольку в этом примере у сервера нет аргументов, оставлен только argv [ 0 ]


    • Чтобы протестировать сервер, необходимо заставить inetd перечитать свой конфигурационный файл (в большинстве реализаций для этого нужно послать ему сигнал SIGHUP) и соединиться с помощью telnet:

      bsd: $ telnet localhost rlnum

      Trying 127.0.0.1. . .

      Connected to localhost

      Escape character is "^]".

      hello

       1: hello

      world

       2: world

      ^]

      telnet> quit

      Connection closed.

      bsd: $


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