Подтвердить что ты не робот

Как установить тайм-аут сокета в C при создании нескольких соединений?

Я пишу простую программу, которая делает несколько подключений к различным серверам для проверки статуса. Все эти соединения построены по требованию; одновременно может быть создано до 10 подключений. Мне не нравится идея one-thread-per-socket, поэтому я сделал все эти клиентские сокеты Non-Blocking и бросил их в пул select().

Он отлично работал, пока мой клиент не жаловался, что время ожидания слишком велико, прежде чем они смогут получить отчет об ошибке, когда целевые серверы перестали отвечать.

Я проверил несколько тем на форуме. Некоторые предположили, что можно использовать сигнал тревоги() или установить тайм-аут в вызове функции select(). Но я имею дело с несколькими соединениями, а не с одним. Когда происходит сигнал тайм-аута процесса, я не могу отличить соединение с таймаутом между всеми другими соединениями.

Есть ли способ изменить длительность тайм-аута по умолчанию?

4b9b3361

Ответ 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;
}