Как установить тайм-аут сокета в C при создании нескольких соединений?
Я пишу простую программу, которая делает несколько подключений к различным серверам для проверки статуса. Все эти соединения построены по требованию; одновременно может быть создано до 10 подключений. Мне не нравится идея one-thread-per-socket, поэтому я сделал все эти клиентские сокеты Non-Blocking и бросил их в пул select().
Он отлично работал, пока мой клиент не жаловался, что время ожидания слишком велико, прежде чем они смогут получить отчет об ошибке, когда целевые серверы перестали отвечать.
Я проверил несколько тем на форуме. Некоторые предположили, что можно использовать сигнал тревоги() или установить тайм-аут в вызове функции select(). Но я имею дело с несколькими соединениями, а не с одним. Когда происходит сигнал тайм-аута процесса, я не могу отличить соединение с таймаутом между всеми другими соединениями.
Есть ли способ изменить длительность тайм-аута по умолчанию?
Ответы
Ответ 1
Вы можете использовать опции сокета SO_RCVTIMEO и SO_SNDTIMEO для установки тайм-аутов для любых операций сокета, например:
struct timeval timeout;
timeout.tv_sec = 10;
timeout.tv_usec = 0;
if (setsockopt (sockfd, SOL_SOCKET, SO_RCVTIMEO, (char *)&timeout,
sizeof(timeout)) < 0)
error("setsockopt failed\n");
if (setsockopt (sockfd, SOL_SOCKET, SO_SNDTIMEO, (char *)&timeout,
sizeof(timeout)) < 0)
error("setsockopt failed\n");
Изменить: на странице setsockopt
:
SO_SNDTIMEO
- это опция для установки значения тайм-аута для операций вывода. Он принимает параметр struct timeval с количеством секунд и микросекунд, используемых для ограничения ожидания завершения выходных операций. Если операция отправки заблокирована в течение этого времени, она возвращается с частичным счетчиком или с ошибкой EWOULDBLOCK, если данные не были отправлены. В текущей реализации этот таймер перезапускается каждый раз, когда к протоколу доставляются дополнительные данные, подразумевая, что предел применяется к выходным частям, находящимся в диапазоне от отметки с низким уровнем воды до отметки высокой воды для вывода.
SO_RCVTIMEO
- это опция для установки значения тайм-аута для операций ввода. Он принимает параметр struct timeval с количеством секунд и микросекунд, используемых для ограничения ожидания завершения операций ввода. В текущей реализации этот таймер перезапускается каждый раз, когда по протоколу принимаются дополнительные данные, и, таким образом, предел является таймером бездействия. Если операция приема была заблокирована в течение этого времени без получения дополнительных данных, она возвращается с коротким счетчиком или с ошибкой EWOULDBLOCK, если данные не были получены. Параметр structvalval времени должен представлять собой положительный временной интервал; в противном случае setsockopt() возвращается с ошибкой EDOM.
Ответ 2
Я не уверен, что полностью понимаю проблему, но думаю, что это связано с тем, что у меня было, я использую Qt с поддержкой сокета TCP, все неблокирующие, как Windows, так и Linux..
захотелось получить быстрое уведомление, когда уже подключенный клиент потерпел неудачу или полностью исчез, и не дождался значения по умолчанию 900+ секунд, пока не будет поднят сигнал разъединения. Трюк для этой работы заключался в том, чтобы установить параметр сокета TCP_USER_TIMEOUT уровня SOL_TCP в требуемое значение, заданное в миллисекундах.
это сравнительно новый вариант, см. http://tools.ietf.org/html/rfc5482, но, судя по всему, он работает нормально, попробовал его с помощью WinXP, Win7/x64 и Kubuntu 12.04/x64, мой выбор из 10 с оказался немного длиннее, но намного лучше, чем все, что я пробовал раньше; -)
Единственная проблема, с которой я столкнулся, заключалась в том, чтобы найти правильное включение, поскольку, по-видимому, это не добавлено к стандартному сокету (все же...), поэтому, наконец, я сам определил их следующим образом:
#ifdef WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#endif
#ifndef SOL_TCP
#define SOL_TCP 6 // socket options TCP level
#endif
#ifndef TCP_USER_TIMEOUT
#define TCP_USER_TIMEOUT 18 // how long for loss retry before timeout [ms]
#endif
настройка этой опции сокета работает только тогда, когда клиент уже подключен, строки кода выглядят следующим образом:
int timeout = 10000; // user timeout in milliseconds [ms]
setsockopt (fd, SOL_TCP, TCP_USER_TIMEOUT, (char*) &timeout, sizeof (timeout));
и сбой начального соединения поймается таймером, запущенным при вызове connect(), поскольку для этого не будет сигнала Qt, сигнал подключения не будет поднят, так как не будет никакого соединения, и сигнал разъединения также не будет поднят, так как пока не было соединения.
Ответ 3
Не можете ли вы реализовать свою собственную систему тайм-аута?
Сохраняйте отсортированный список или, еще лучше, кучу приоритетов, как показывает Хит, событий таймаута. В ваших вызовах выбора или опроса используется значение таймаута в верхней части списка таймаута. Когда этот тайм-аут поступит, выполните действие, прилагаемое к этому таймауту.
Это действие может закрывать сокет, который еще не подключен.
Ответ 4
connect
timeout должен обрабатываться с неблокирующим сокетом (GNU LibC documentation на connect
). Вы получите connect
для немедленного возврата, а затем используйте select
для ожидания с истечением времени ожидания завершения соединения.
Это также объясняется здесь: Ошибка выполнения операции при ошибке подключения (функции).
int wait_on_sock(int sock, long timeout, int r, int w)
{
struct timeval tv = {0,0};
fd_set fdset;
fd_set *rfds, *wfds;
int n, so_error;
unsigned so_len;
FD_ZERO (&fdset);
FD_SET (sock, &fdset);
tv.tv_sec = timeout;
tv.tv_usec = 0;
TRACES ("wait in progress tv={%ld,%ld} ...\n",
tv.tv_sec, tv.tv_usec);
if (r) rfds = &fdset; else rfds = NULL;
if (w) wfds = &fdset; else wfds = NULL;
TEMP_FAILURE_RETRY (n = select (sock+1, rfds, wfds, NULL, &tv));
switch (n) {
case 0:
ERROR ("wait timed out\n");
return -errno;
case -1:
ERROR_SYS ("error during wait\n");
return -errno;
default:
// select tell us that sock is ready, test it
so_len = sizeof(so_error);
so_error = 0;
getsockopt (sock, SOL_SOCKET, SO_ERROR, &so_error, &so_len);
if (so_error == 0)
return 0;
errno = so_error;
ERROR_SYS ("wait failed\n");
return -errno;
}
}
Ответ 5
Конечно, первый ответ - ЛУЧШИЙ.
Могу я добавить что-то?
...
После 2 setsockopt
Вы можете контролировать, прошел ли клиент тест таймаута или не удалось с этим:
после
n = readline(sockd, recvline, MAXLINE);
вам нужно вставить
if (n <= 0){
if(write(sockd,"ERROR. Timeout di 5sec scaduto, sii piu' veloce\n",MAXLINE)<0)
err_sys("errore nella write");
close(sockd);
sockd = 0;
break;
}