web-dev-qa-db-fra.com

Comment définir le délai d'attente de la prise en C lors de l'établissement de plusieurs connexions?

J'écris un programme simple qui établit plusieurs connexions à différents serveurs pour la vérification de l'état. Toutes ces connexions sont construites à la demande; jusqu'à 10 connexions peuvent être créées simultanément. Je n'aime pas l'idée d'un thread par socket. J'ai donc fait en sorte que tous ces sockets client ne soient pas bloquants et je les ai jetés dans un pool select ().

Cela a très bien fonctionné, jusqu'à ce que mon client se plaint du délai d'attente trop long avant de pouvoir obtenir le rapport d'erreur lorsque les serveurs cibles ont cessé de répondre.

J'ai vérifié plusieurs sujets sur le forum. Certains avaient suggéré d'utiliser le signal alarm () ou de définir un délai d'attente dans l'appel de la fonction select (). Mais je traite avec plusieurs connexions, au lieu d'une. Lorsqu'un signal de délai d'expiration de processus large se produit, je n'ai aucun moyen de distinguer la connexion de délai d'expiration parmi toutes les autres connexions.

Est-il possible de changer la durée du délai d'attente par défaut du système?

61
RichardLiu

Vous pouvez utiliser les options de socket SO_RCVTIMEO et SO_SNDTIMEO pour définir des délais d'expiration pour toutes les opérations de socket, comme suit:

    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");

Modifier: à partir de setsockoptpage de manuel :

SO_SNDTIMEO est une option permettant de définir une valeur de délai d'attente pour les opérations de sortie. Il accepte un paramètre struct timeval avec le nombre de secondes et de microsecondes utilisé pour limiter les temps d'attente avant la fin des opérations de sortie. Si une opération d'envoi a été bloquée pendant autant de temps, elle retourne avec un compte partiel ou avec l'erreur EWOULDBLOCK si aucune donnée n'a été envoyée. Dans l'implémentation actuelle, ce temporisateur est redémarré à chaque fois que des données supplémentaires sont fournies au protocole, ce qui implique que la limite s'applique aux parties en sortie dont la taille va de la laisse de basse mer à la laisse de haute mer.

SO_RCVTIMEO est une option permettant de définir une valeur de délai d'attente pour les opérations d'entrée. Il accepte un paramètre struct timeval avec le nombre de secondes et de microsecondes utilisé pour limiter les temps d'attente avant la fin des opérations d'entrée. Dans l'implémentation actuelle, ce temporisateur est redémarré à chaque fois que des données supplémentaires sont reçues par le protocole. La limite est donc effectivement un temporisateur d'inactivité. Si une opération de réception a été bloquée pendant autant de temps sans recevoir de données supplémentaires, elle est renvoyée avec un compte court ou avec l'erreur EWOULDBLOCK si aucune donnée n'a été reçue. Le paramètre struct timeval doit représenter un intervalle de temps positif; sinon, setsockopt () renvoie l'erreur EDOM.

110
Toby

je ne suis pas sûr de bien comprendre le problème, mais je suppose qu'il est lié à celui que j'avais, j'utilise Qt avec TCP communication par socket, tous non bloquants, Windows et Linux ..

voulait obtenir une notification rapide lorsqu'un client déjà connecté échouait ou avait complètement disparu, et n'attendait pas plus de 900 secondes par défaut jusqu'à ce que le signal de déconnexion soit émis. L'astuce pour que cela fonctionne est de définir l'option de socket TCP_USER_TIMEOUT de la couche SOL_TCP sur la valeur requise, exprimée en millisecondes.

c’est une nouvelle option comparable, voir http://tools.ietf.org/html/rfc5482 , mais apparemment cela fonctionne bien, essayé avec WinXP, Win7/x64 et Kubuntu 12.04/x64, mon choix de 10 s s'est avéré être un peu plus long, mais bien meilleur que tout ce que j'ai pu essayer auparavant ;-)

le seul problème que j'ai rencontré était de trouver le bon include, comme ce n'est apparemment pas ajouté au socket standard (encore ...), donc je les ai finalement définis moi-même comme suit:

#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

la définition de cette option de socket ne fonctionne que lorsque le client est déjà connecté, les lignes de code se présentent comme suit:

int timeout = 10000;  // user timeout in milliseconds [ms]
setsockopt (fd, SOL_TCP, TCP_USER_TIMEOUT, (char*) &timeout, sizeof (timeout));

et l'échec d'une connexion initiale est capturé par un temporisateur lancé lors de l'appel de connect (), car il n'y aura aucun signal Qt pour cela, le signal de connexion ne sera pas élevé, car il n'y aura pas de connexion et le signal de déconnexion aussi ne pas être soulevé, car il n'y a pas encore eu de connexion ..

13
wgr

Ne pouvez-vous pas mettre en place votre propre système de timeout?

Conservez une liste triée, ou mieux encore, un lieu prioritaire, comme le suggère Heath, d'événements de dépassement de délai. Dans vos appels de sélection ou d’interrogation, utilisez la valeur de délai en haut de la liste des délais. Lorsque ce délai arrive, effectuez l'action associée à ce délai.

Cette action pourrait être la fermeture d'un socket qui ne s'est pas encore connecté.

9
Zan Lynx

connect le délai d'attente doit être géré avec un socket non bloquant (GNU LibC documentation sur connect). Vous obtenez connect de revenir immédiatement, puis utilisez select pour attendre avec un délai d'attente la fin de la connexion.

Ceci est également expliqué ici: Erreur d'opération en cours sur erreur de connexion (fonction) .

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;
    }
}
4
Couannette