13.2. TCP-версии серверов
echo
,
discard
и
chargen
запускаются как дочерние процессы, после того как демон
inetd
вызовет функцию
fork
, поскольку эти три сервера работают, пока клиент не прервет соединение. Два других TCP-сервера,
time
и
daytime
, не требуют использования функции
fork
, поскольку эти службы легко реализовать (получить текущую дату, преобразовать ее, записать и закрыть соединение). Эти два сервера обрабатываются непосредственно демоном
inetd
. Все пять UDP-служб обрабатываются без использования функции
fork
, поскольку каждая из них генерирует единственную дейтаграмму в ответ на клиентскую дейтаграмму, которая запускает эту службу. Эти пять служб обрабатываются напрямую демоном
inetd
.
13.3. Это известная атака типа «отказ в обслуживании» [18]. Первая дейтаграмма с порта 7 заставляет сервер
chargen
отправить дейтаграмму обратно на порт 7. На эту дейтаграмму приходит эхо-ответ, и серверу
chargen
посылается другая дейтаграмма. Происходит зацикливание. Одним из решений, реализованным в системе BSD/OS, является игнорирование дейтаграмм, направленных любому внутреннему серверу, если номер порта отправителя пришедшей дейтаграммы принадлежит одному из внутренних серверов. Другим решением может быть запрещение этих внутренних служб — либо с помощью демона
inetd
на каждом узле, либо на маршрутизаторе, связывающем внутреннюю сеть организации с Интернетом.
13.4. IP-адрес и номер порта клиента могут быть получены из структуры адреса сокета, заполняемой функцией
accept
.
Причина, по которой демон
inetd
не делает этого для UDP-сокета, состоит в том, что чтение дейтаграмм (
recvfrom
) осуществляется с помощью функции
exec
сервером, а не самим демоном
inetd
.
Демон
inetd
может считать дейтаграмму с флагом
MSG_PEEK
(см. раздел 14.7), только чтобы получить IP-адрес и номер порта клиента, но оставляет саму дейтаграмму для чтения серверу.
Глава 14
14.1. Если не установлен обработчик, первый вызов функции
signal
будет возвращать значение
SIG_DFL
, а вызов функции
signal
для восстановления обработчика просто вернет его в исходное состояние.
14.3. Приведем цикл
for
:
for (;;) {
if ((n = Recv(sockfd, recvline, MAXLINE, MSG_PEEK)) == 0)
break; /* сервер закрыл соединение */
Ioctl(sockfd, FIONREAD, npend);
printf("%d bytes from PEEK, %d bytes pending\n", n, npend);
n = Read(sockfd, recvline, MAXLINE);
recvline[n] = 0; /* завершающий нуль */
Fputs(recvline, stdout);
}
14.4. Данные продолжают выводиться, поскольку выход из функции
main
— это то же самое, что и возврат из этой функции. Функция
main
вызывается программой запуска на языке С следующим образом:
exit(main(argc, argv));
Следовательно, вызывается функция
exit
, а затем и программа очистки стандартного ввода-вывода.
Глава 15
15.1. Функция
unlink
удаляет имя файла из файловой системы, и когда клиент позже вызовет функцию
connect
, она не выполнится. Это не влияет на прослушиваемый сокет сервера, но клиенты не смогут выполнить функции
connect
после вызова функции
unlink
.
15.2. Клиент не сможет соединиться с сервером с помощью функции
connect
, даже если полное имя существует, поскольку для успешного соединения с помощью функции
connect
доменный сокет Unix должен быть открыт и связан с этим полным именем (см. раздел 15.4).
15.3. При выводе адреса протокола клиента путем вызова функции
sock_ntop
мы получим сообщение
datagram from (no pathname bound)
(дейтаграмма от (имя не задано)), поскольку по умолчанию с сокетом клиента не связывается никакое имя.
Одним из решений является проверить доменный сокет Unix в функциях
udp_client
и
udp_connect
и связать с сокетом при помощи функции
bind
временное полное имя. Это приведет к зависимости от протокола в библиотечной функции, но не в нашем приложении.
15.4. Даже если мы заставим сервер вернуть в функции
write
1 байт на его 26- байтовый ответ, использование функции
sleep
на стороне клиента гарантирует, что все 26 сегментов будут получены до вызова функции
read
, в результате чего функция
read
вернет полный ответ. Это еще одно подтверждение того, что TCP является потоком байтов с отсутствием границ записей.
Чтобы использовать доменные протоколы Unix, запускаем клиент и сервер с двумя аргументами командной строки
/lосаl
(или
/unix
) и
/tmp/daytime
(или любое другое временное имя, которое вы хотите использовать). Ничего не изменится: 26 байт будут возвращаться функцией
read
каждый раз, когда будет запускаться клиент.