UNIX: разработка сетевых приложений
Мы также рассказываем о том, как определить, сколько данных находится в приемном буфере сокета и как использовать с сокетами стандартную библиотеку ввода-вывода С, и обсуждаем более совершенные способы ожидания событий.
14.2. Тайм-ауты сокета
Существует три способа установки тайм-аута для операции ввода-вывода через сокет.
1. Вызов функции
, которая генерирует сигналalarm, когда истекает заданное время. Это подразумевает обработку сигналов, которая может варьироваться от одной реализации к другой. К тому же такой подход может стать помехой другим существующим вызовам функцииSIGALRMв данном процессе.alarm2. Блокирование при ожидании ввода-вывода в функции
, имеющей встроенное ограничение времени, вместо блокирования в вызове функцииselectилиread.write3. Использование более новых параметров сокета —
иSO_RCVTIMEO. Проблема при использовании этого подхода заключается в том, что не все реализации поддерживают новые параметры сокетов.SO_SNDTIMEOВсе три технологии работают с функциями ввода и вывода (такими как
,readи их вариациями, напримерwriteиrecvfrom), но нам также хотелось бы иметь технологию, работающую с функциейsendto, поскольку процесс соединения TCP может занять длительное время (обычно 75 с). Функциюconnectможно использовать для установки тайм-аута функцииselect, только когда сокет находится в неблокируемом режиме (который мы рассматриваем в разделе 16.3), а параметры сокетов, устанавливающие тайм-аут, не работают с функциейconnect. Мы также должны отметить, что первые две технологии работают с любым дескриптором, в то время как третья технология только с дескрипторами сокетов.connectТеперь мы представим примеры применения всех трех технологий.
Тайм-аут для функции connect (сигнал SIGALRM)
В листинге 14.1 [1] показана наша функция
, вызывающая функцию connect с ограничением по времени, заданным вызывающим процессом. Первые три аргумента — это аргументы, которых требует функцияconnect_timeo, а четвертый — это длительность ожидания в секундах.connectЛистинг 14.1. Функция connect с тайм-аутом
//lib/connect_timeo.c1 #include "unp.h"2 static void connect_alarm(int);3 int4 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 void21 connect_alarm(int signo)22 {23 return; /* просто прерываем connect() */24 }Установка обработчика сигналовДля8устанавливается обработчик сигнала. Текущий обработчик сигнала (если таковой имеется) сохраняется, и таким образом мы можем восстановить его в конце функции.SIGALRMУстановка таймераТаймер для процесса устанавливается на время (число секунд), заданное вызывающим процессом. Возвращаемое значение функции9-10— это число секунд, остающихся в таймере для процесса (если он уже установлен для процесса) в настоящий момент или 0 (если таймер не был установлен прежде). В первом случае мы выводим сообщение с предупреждением, поскольку мы стираем предыдущую установку таймера (см. упражнение 14.2).alarmВызов функции connectВызывается функция11-15, и если функция прерывается (connect), мы присваиваем переменной errno значениеEINTR. Сокет закрывается, чтобы не допустить продолжения трехэтапного рукопожатия.ETIMEDOUTВыключение таймера и восстановление предыдущего обработчика сигналаТаймер при обнулении выключается, и восстанавливается предыдущий обработчик сигналов (если таковой имеется).16-18Обработка сигнала SIGALRMОбработчик сигнала просто возвращает управление. Предполагается, что это прервет ожидание функции20-24, заставив ее возвратить ошибкуconnect. Вспомните нашу функциюEINTR(см. листинг 5.5), которая не устанавливает флагаsignal, когда перехватываемый сигнал — это сигналSA_RESTART.SIGALRMОдним из важных моментов в этом примере является то, что мы всегда можем сократить период ожидания для функции
, используя эту технологию, но мы не можем увеличить период, заданный для ядра. В Беркли-ядре тайм-аут для функцииconnectобычно равен 75 с. Мы можем задать меньшее значение для нашей функции, допустим 10, но если мы задаем большее значение, скажем 80, тайм- аут самой функцииconnectвсе равно составит 75 с.connectДругой важный момент в данном примере — то, что мы используем возможность прерывания системного вызова (
) для того, чтобы возвратить управление, прежде чем истечет время ожидания ядра. Такой подход допустим, когда мы выполняем системный вызов и можем обработать возвращение ошибкиconnect. Но в разделе 29.7 мы встретимся с библиотечной функцией, выполняющей системный вызов, которая сама выполняет заново системный вызов при возвращении ошибкиEINTR. Мы можем продолжать работать с сигналомEINTRи в этом случае, но в листинге 29.6 мы увидим, что нам придется воспользоваться функциямиSIGALRMиsigsetjmp, поскольку библиотечная функция игнорирует ошибкуsiglongjmp.EINTR