3. Создается новый процесс, который обрабатывает входящие UDP-датаграммы или TCP-соединения. Этот процесс автоматически переходит в фоновый режим. Демон inetd берет на себя всю работу по созданию потомков с помощью вызова fork() и уничтожению завершившихся процессов (задействуя обработчик SIGCHLD).
4. Файловым дескрипторам с номерами 0, 1 и 2 присваиваются копии дескриптора для UDP- или TCP-сокета; все остальные файловые дескрипторы закрываются (так как не будут использоваться запущенным сервером).
5. Запускается серверная программа.
В описании вышеприведенных шагов мы исходим из того, что поле с флагом в файле /etc/inetd.conf содержит значение nowait для TCP-служб и wait для UDP-служб (так обычно и происходит).
Пример того, как inetd упрощает написание TCP-служб, приводится в листинге 56.6, где показан эквивалент сервера echo из листинга 56.4, который запускается с помощью inetd. Поскольку этот демон берет на себя все действия, перечисленные выше, нам остается лишь запрограммировать поведение дочерних процессов, обрабатывающих клиентские запросы, доступные через файловый дескриптор 0 (STDIN_FILENO).
Допустим, сервер находится в каталоге /bin. Тогда, чтобы демон inetd смог его запустить, мы должны создать в файле /etc/inetd.conf следующую запись:
echo stream tcp nowait root /bin/is_echo_inetd_sv is_echo_inetd_sv
Листинг 56.6. TCP-сервер echo, рассчитанный на запуск с помощью inetd
sockets/is_echo_inetd_sv.c
#include
#include "tlpi_hdr.h"
#define BUF_SIZE 4096
int
main(int argc, char *argv[])
{
char buf[BUF_SIZE];
ssize_t numRead;
while ((numRead = read(STDIN_FILENO, buf, BUF_SIZE)) > 0) {
if (write(STDOUT_FILENO, buf, numRead)!= numRead) {
syslog(LOG_ERR, "write() failed: %s", strerror(errno));
exit(EXIT_FAILURE);
}
}
if (numRead == -1) {
syslog(LOG_ERR, "Error from read(): %s", strerror(errno));
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
sockets/is_echo_inetd_sv.c
Итерационный сервер обслуживает по одному клиенту за раз: сначала обрабатывает запрос(-ы) одного клиента и только потом переходит к следующему. Параллельные серверы выполняют одновременную обработку нескольких клиентских запросов. Традиционная архитектура параллельного сервера, которая подразумевает создание нового дочернего процесса (или потока) для каждого клиента, может оказаться не самым оптимальным решением при высокой нагрузке; в связи с этим мы выделили несколько альтернативных подходов для параллельной обработки большого количества запросов.
Демон inetd следит за несколькими сокетами и в ответ на входящие UDP-датаграммы или TCP-соединения запускает подходящие серверы. Использование демона позволяет снизить нагрузку на систему путем минимизации количества запущенных серверных процессов, а также упрощает написание серверного кода, так как inetd берет на себя большую часть шагов, связанных с инициализацией.
Ознакомьтесь с источниками, приведенными в разделе 55.14.
56.1. Добавьте в программу из листинга 56.4 (is_echo_sv.c) код, ограничивающий максимальное количество дочерних процессов, выполняемых одновременно.
56.2. Иногда возникает необходимость в написании сервера, который можно запускать как напрямую, из командной строки, так и через inetd. Для различения этих двух случаев используются параметры командной строки. Измените программу из листинга 56.4 так, чтобы в случае обнаружения параметра — i она считала, что ее запустили с помощью inetd. При этом программа должна обслуживать только одного клиента, подключенного к ее сокету путем файлового дескриптора STDIN_FILENO, предоставленного демоном inetd. Если параметр — i не указан, то программа считает, что ее запустили из командной строки, и выполняется в традиционной манере (для внесения данного изменения требуется всего лишь несколько дополнительных строчек кода). Отредактируйте файл /etc/inetd.conf, чтобы эта программа запускалась в ответ на вызов службы echo.
57. Сокеты: углубленный материал
В этой главе рассмотрен ряд продвинутых тем, связанных с написанием программ на основе сокетов, включая следующие:
• обстоятельства, в которых работа с потоковыми сокетами приводит к частичному чтению или записи;
• использование функции shutdown() для закрытия одной из ветвей двунаправленного канала между сокетами;
• системные вызовы recv() и send(), предоставляющие специфические возможности, связанные с сокетами и недоступные для операций read() и write();
• системный вызов sendfile(), в некоторых ситуациях позволяющий эффективно выводить данные в сокет;
• детали реализации протокола TCP, призванные развенчать ряд распространенных заблуждений, способных привести к ошибкам при написании программ на основе потоковых сокетов;
• использование команд netstat и tcpdump для мониторинга и отладки приложений, задействующих сокеты;