KnigaRead.com/
KnigaRead.com » Компьютеры и Интернет » Программное обеспечение » Уильям Стивенс - UNIX: разработка сетевых приложений

Уильям Стивенс - UNIX: разработка сетевых приложений

На нашем сайте KnigaRead.com Вы можете абсолютно бесплатно читать книгу онлайн Уильям Стивенс, "UNIX: разработка сетевых приложений" бесплатно, без регистрации.
Перейти на страницу:

3. Что произойдет, если мы создадим сокет UDP, свяжем порт 7 с сокетом (стандартный эхо-сервер в табл. 2.1) и отправим дейтаграмму UDP-серверу chargen?

4. В руководстве Solaris 2.x для демона inetd описывается флаг -t, заставляющий демон inetd вызывать функцию syslog (с аргументами facility=LOG_DAEMON и level=LOG_NOTICE) для протоколирования клиентского IP-адреса и порта любой службы TCP, которые обрабатывает демон inetd. Как демон inetd получает эту информацию?

В этом же руководстве сказано, что демон inetd не может выполнить это для сокета UDP. Почему?

Есть ли способ обойти эти ограничения для служб UDP?

Глава 14

Дополнительные функции ввода-вывода

14.1. Введение

Эта глава охватывает разнообразные функции и технологии, которые мы помещаем в общую категорию «расширенного ввода-вывода». Сначала мы описываем установку тайм-аута для операции ввода-вывода, которую можно выполнить тремя различными способами. Затем мы рассматриваем три варианта функций read и write: recv и send, допускающие четвертый аргумент, содержащий флаги, передаваемые от процесса к ядру; readv и writev, позволяющие нам задавать массив буферов для ввода или вывода; recvmsg и sendmsg, объединяющие все свойства других функций ввода-вывода и обладающие новой возможностью получения и отправки вспомогательных данных.

Мы также рассказываем о том, как определить, сколько данных находится в приемном буфере сокета и как использовать с сокетами стандартную библиотеку ввода-вывода С, и обсуждаем более совершенные способы ожидания событий.

14.2. Тайм-ауты сокета

Существует три способа установки тайм-аута для операции ввода-вывода через сокет.

1. Вызов функции alarm, которая генерирует сигнал SIGALRM, когда истекает заданное время. Это подразумевает обработку сигналов, которая может варьироваться от одной реализации к другой. К тому же такой подход может стать помехой другим существующим вызовам функции alarm в данном процессе.

2. Блокирование при ожидании ввода-вывода в функции select, имеющей встроенное ограничение времени, вместо блокирования в вызове функции read или write.

3. Использование более новых параметров сокета — SO_RCVTIMEO и SO_SNDTIMEO. Проблема при использовании этого подхода заключается в том, что не все реализации поддерживают новые параметры сокетов.

Все три технологии работают с функциями ввода и вывода (такими как read, write и их вариациями, например recvfrom и sendto), но нам также хотелось бы иметь технологию, работающую с функцией connect, поскольку процесс соединения TCP может занять длительное время (обычно 75 с). Функцию select можно использовать для установки тайм-аута функции connect, только когда сокет находится в неблокируемом режиме (который мы рассматриваем в разделе 16.3), а параметры сокетов, устанавливающие тайм-аут, не работают с функцией connect. Мы также должны отметить, что первые две технологии работают с любым дескриптором, в то время как третья технология только с дескрипторами сокетов.

Теперь мы представим примеры применения всех трех технологий.

Тайм-аут для функции connect (сигнал SIGALRM)

В листинге 14.1[1] показана наша функция connect_timeo, вызывающая функцию connect с ограничением по времени, заданным вызывающим процессом. Первые три аргумента — это аргументы, которых требует функция connect, а четвертый — это длительность ожидания в секундах.

Листинг 14.1. Функция connect с тайм-аутом

//lib/connect_timeo.c

 1 #include "unp.h"


 2 static void connect_alarm(int);


 3 int

 4 connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)

 5 {

 6  Sigfunc *sigfunc;

 7  int n;


 8  sigfunc = Signal(SIGALRM, connect_alarm);

 9  if (alarm(nsec) != 0)

10   err_msg("connect_timeo: alarm was already set");


11  if ((n = connect(sockfd, saptr, salen)) < 0) {

12   close(sockfd);

13   if (errno == EINTR)

14    errno = ETIMEDOUT;

15  }

16  alarm(0); /* отключение alarm */

17  Signal(SIGALRM, sigfunc); /* восстанавливаем прежний обработчик

                                 сигнала */

18  return (n);

19 }


20 static void

21 connect_alarm(int signo)

22 {

23  return; /* просто прерываем connect() */

24 }

Установка обработчика сигналов

8 Для SIGALRM устанавливается обработчик сигнала. Текущий обработчик сигнала (если таковой имеется) сохраняется, и таким образом мы можем восстановить его в конце функции.

Установка таймера

9-10 Таймер для процесса устанавливается на время (число секунд), заданное вызывающим процессом. Возвращаемое значение функции alarm — это число секунд, остающихся в таймере для процесса (если он уже установлен для процесса) в настоящий момент или 0 (если таймер не был установлен прежде). В первом случае мы выводим сообщение с предупреждением, поскольку мы стираем предыдущую установку таймера (см. упражнение 14.2).

Вызов функции connect

11-15 Вызывается функция connect, и если функция прерывается (EINTR), мы присваиваем переменной errno значение ETIMEDOUT. Сокет закрывается, чтобы не допустить продолжения трехэтапного рукопожатия.

Выключение таймера и восстановление предыдущего обработчика сигнала

16-18 Таймер при обнулении выключается, и восстанавливается предыдущий обработчик сигналов (если таковой имеется).

Обработка сигнала SIGALRM

20-24 Обработчик сигнала просто возвращает управление. Предполагается, что это прервет ожидание функции connect, заставив ее возвратить ошибку EINTR. Вспомните нашу функцию signal (см. листинг 5.5), которая не устанавливает флага SA_RESTART, когда перехватываемый сигнал — это сигнал SIGALRM.

Одним из важных моментов в этом примере является то, что мы всегда можем сократить период ожидания для функции connect, используя эту технологию, но мы не можем увеличить период, заданный для ядра. В Беркли-ядре тайм-аут для функции connect обычно равен 75 с. Мы можем задать меньшее значение для нашей функции, допустим 10, но если мы задаем большее значение, скажем 80, тайм- аут самой функции connect все равно составит 75 с.

Другой важный момент в данном примере — то, что мы используем возможность прерывания системного вызова (connect) для того, чтобы возвратить управление, прежде чем истечет время ожидания ядра. Такой подход допустим, когда мы выполняем системный вызов и можем обработать возвращение ошибки EINTR. Но в разделе 29.7 мы встретимся с библиотечной функцией, выполняющей системный вызов, которая сама выполняет заново системный вызов при возвращении ошибки EINTR. Мы можем продолжать работать с сигналом SIGALRM и в этом случае, но в листинге 29.6 мы увидим, что нам придется воспользоваться функциями sigsetjmp и siglongjmp, поскольку библиотечная функция игнорирует ошибку EINTR.

Тайм-аут для функции recvfrom (сигнал SIGALRM)

В листинге 14.2 показана новая версия функции dg_cli, приведенной в листинге 8.4, в которую добавлен вызов функции alarm для прерывания функции recvfrom при отсутствии ответа в течение 5 с.

Листинг 14.2. Функция dg_cli, в которой при установке тайм-аута для функции recvfrom используется функция alarm

//advio/dgclitimeo3.c

 1 #include "unp.h"


 2 static void signalrm(int);


 3 void

 4 dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)

 5 {

 6  int n;

 7  char sendline[MAXLINE], recvline[MAXLINE + 1];


 8  Signal(SIGALRM, signalrm);


 9  while (Fgets(sendline, MAXLINE, fp) != NULL) {


10   Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);


11   alarm(5);

12   if ((n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {

13    if (errno == EINTR)

14     fprintf(stderr, "socket timeoutn");

15    else

16     err_sys("recvfrom error");

17   } else {

18    alarm(0);

19    recvline[n] = 0; /* завершающий нуль */

20    Fputs(recvline, stdout);

21   }

22  }

23 }


24 static void

25 sig_alrm(int signo)

26 {

27  return; /* просто прерываем recvfrom() */

28 }

Обработка тайм-аута из функции recvfrom

8-22 Мы устанавливаем обработчик для сигнала SIGALRM и затем вызываем функцию alarm для 5-секундного тайм-аута при каждом вызове функции recvfrom. Если функция recvfrom прерывается нашим обработчиком сигнала, мы выводим сообщение об ошибке и продолжаем работу. Если получена строка от сервера, мы отключаем функцию alarm и выводим ответ.

Перейти на страницу:
Прокомментировать
Подтвердите что вы не робот:*