Использование вызова alarm
Есть два способа прерывания connect по тайм-ауту. Самый простой - окружить этот вызов обращениями к alarm. Предположим, например, что вы не хотите ждать завершения connect более пяти секунд. Тогда можно модифицировать каркас tcpclient. skel (листинг 2.6), добавив простой обработчик сигнала и немного видоизменив функцию main:
void alarm_hndlr (int sig)
{
return;
}
int main ( int argc, char **argv )
{
…
signal ( SIGALRM, alarm_hndlr );
alarm( 5 );
rc = connect(s, ( struct sockaddr * )&peer, sizeof( peer ) )
alarm( 0 );
if ( rc < 0 )
{
if ( errno == EINTR )
error( 1, 0, "истек тайм-аут connect\n" );
…
}
Назовем программу, созданную по этому каркасу, connecto и попытаемся с помощью соединиться с очень загруженным Web-сервером Yahoo. Получите ожидаемый результат:
bsd: $ connectto yahoo.com daytime
connectto: истек тайм-аут connect спустя 5 с
bsd: $
Хотя это и простое решение, с ним связано две потенциальных проблемы. Сначала обсудим их, а потом рассмотрим другой метод - он сложнее, но лишен этих недостатков.
Прежде всего в данном примере подразумевается, что «тревожный» таймер, используемый в вызове alarm, нигде в программе не применяется, и, значит, для сигнала SIGALRM не установлен другой обработчик. Если таймер уже взведен где-то еще, то приведенный код его переустановит, поэтому старый таймер не сработает. Правильнее было бы сохранить и затем восстановить время, оставшееся до срабатывания текущего таймера (его возвращает вызов alarm), а также сохранить и восстановить текущий обработчик сигнала SIGALRM (его адрес возвращает вызов signal). Чтобы все было корректно, надо было также получить время, проведенное в вызове connect, и вычесть его из времени, оставшегося до срабатывания исходного таймера.
Далее, для упрощения вы завершаете клиент, если connect не вернул управления вовремя. Вероятно, нужно было бы предпринять иные действия. Однако надо иметь в виду, что перезапустить connect нельзя. Дело в том, что в результате вызова connect сокет остался привязанным к ранее указанному адресу, так что попытка повторного выполнения приведет к ошибке «Address already in use». При желании повторить connect, возможно, немного подождав, придется сначала закрыть, а затем заново открыть сокет, вызвав close (или closesocket) и socket.
Еще одна потенциальная проблема в том, что некоторые UNIX- системы могут автоматически возобновлять вызов connect после возврата из обработчика сигнала. В таком случае connect не вернет управления, пока не истечет тайм-аут TCP. Во всех современных вариантах системы UNIX поддерживается вызов sigaction, который можно использовать вместо signal. В таком случае следует указать, хотите ли вы рестартовать connect. Но в некоторых устаревших версиях UNIX этот вызов не поддерживается, и тогда использование alarm для прерыва ния connect по тайм-ауту затруднительно.
Если нужно вывести всего лишь диагностическое сообщение и завершить сеанс, то это можно сделать в обработчике сигнала. Поскольку это происходит до рес тарта connect, не имеет значения, поддерживает система вызов sigaction или не. Однако если нужно предпринять какие-то другие действия, то, вероятно, придется выйти из обработчика с помощью функции longjmp, а это неизбежно приводит к возникновению гонки.
Примечание: Следует заметить, что гонка возникает и в более простом случае, когда вы завершаете программу. Предположим, что соединение успешно установлено, и connect вернул управление. Однако прежде чем вы успели его отменить, таймер сработал, что привело к вызову обработчика сигнала и, следовательно, к завершению программы.
alarm( 5 };
rc = connect( s, NULL, NULL );
/* здесь срабатывает таймер */
alarm ( 0 );
Вы завершаете программу, хотя соединение и удалось установить. В первоначальном коде такая гонка не возникает, поскольку даже если таймер сработает между возвратом из connect и вызовом alarm, обработчик сигнала вернет управление, не предпринимая никаких действий.
Принимая это во внимание, многие эксперты считают, что для прерывания вызова connect по тайм-ауту лучше использовать select.