2010-11-15 18 views
56

Estoy escribiendo un programa simple que hace conexiones múltiples a diferentes servidores para la verificación de estado. Todas estas conexiones se construyen a pedido; se pueden crear hasta 10 conexiones simultáneamente. No me gusta la idea de un hilo por zócalo, así que hice que todos estos zócalos para clientes sean no bloqueantes y los eché a un grupo select().¿Cómo establecer el tiempo de espera del socket en C al hacer conexiones múltiples?

Funcionó de maravilla, hasta que mi cliente se quejó de que el tiempo de espera es demasiado largo para poder obtener el informe de errores cuando los servidores de destino dejaban de responder.

He revisado varios temas en el foro. Algunos sugirieron que uno puede usar la señal de alarma() o establecer un tiempo de espera en la llamada a la función de selección(). Pero estoy lidiando con múltiples conexiones, en lugar de una. Cuando ocurre una señal de tiempo de espera amplia de proceso, no tengo manera de distinguir la conexión de tiempo de espera entre todas las otras conexiones.

¿Hay alguna forma de cambiar la duración del tiempo de espera predeterminado del sistema?

+0

¿Quieres decir que connect() tarda demasiado tiempo o que estás alrea ¿Está conectado y atraviesa un largo período cuando no hay nada que leer? – Duck

+0

@Duck: Mi problema es que connect() tarda demasiado en agotar el tiempo de espera. Cada conexión en mi programa es temporalmente; se supone que debe desconectarse inmediatamente después de realizar un procedimiento de reconocimiento de estado. No es necesario ajustar la duración de TCP_KEEP_ALIVE individualmente en mi caso. – RichardLiu

Respuesta

96

Puede utilizar las opciones de conector SO_RCVTIMEO y SO_SNDTIMEO a establecer tiempos de espera para cualquier operación de socket, así:

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

Editar: del setsockoptman page:

SO_SNDTIMEO es una opción para configurar un valor de tiempo de espera para las operaciones de salida. Acepta un parámetro struct timeval con la cantidad de segundos y microsegundos utilizados para limitar las esperas para que finalicen las operaciones de salida. Si una operación de envío se ha bloqueado durante tanto tiempo, regresa con un recuento parcial o con el error EWOULDBLOCK si no se enviaron datos. En la implementación actual, este temporizador se reinicia cada vez que se entregan datos adicionales al protocolo, lo que implica que el límite se aplica a las porciones de salida que varían en tamaño desde la marca de agua baja a la marca de agua alta para la salida.

SO_RCVTIMEO es una opción para establecer un valor de tiempo de espera para las operaciones de entrada. Acepta un parámetro struct timeval con el número de segundos y microsegundos utilizados para limitar las esperas para que finalicen las operaciones de entrada. En la implementación actual, este temporizador se reinicia cada vez que el protocolo recibe datos adicionales y, por lo tanto, el límite es en efecto un temporizador de inactividad. Si una operación de recepción ha sido bloqueada por este tiempo sin recibir datos adicionales, regresa con un conteo breve o con el error EWOULDBLOCK si no se recibieron datos. El parámetro struct timeval debe representar un intervalo de tiempo positivo; de lo contrario, setsockopt() regresa con el error EDOM.

+1

¿Estás seguro de que esto funciona para connect()? Yo no lo creo. – Duck

+0

¿Qué te hace pensar que no funciona para connect()? Estoy seguro de que los he usado para establecer tiempos de espera en las llamadas a connect(). – Toby

+0

@Toby: Lo he intentado, tanto el modo de bloqueo como el modo sin bloqueo, pero no funciona. Estos dos parámetros aparentemente solo funcionan con send() y recv(), no con connect(). – RichardLiu

10

¿No puedes implementar tu propio sistema de tiempo de espera?

Mantenga una lista ordenada, o mejor aún un montón de prioridad como Heath sugiere, de eventos de tiempo de espera. En las llamadas de selección o encuesta use el valor de tiempo de espera desde la parte superior de la lista de tiempos de espera. Cuando llegue ese tiempo de espera, realice esa acción asociada a ese tiempo de espera.

Esa acción podría estar cerrando una toma que aún no se ha conectado.

+0

Hmm ... sería una buena idea, pero tomaría un poco de esfuerzo hacerlo. Esperaba algo así como la llamada a la función setsockopt() que podría establecer la duración del tiempo de espera de conexión individualmente. Por cierto, ¿qué pasaría con select() si cierro un socket pendiente de conexión en otro hilo? ¿Causará algún hilo persiguiendo la situación? – RichardLiu

+0

¿Esta solución es la más limpia y más robusta, y no tiene votos ascendentes? Aquí está el mío. – SquareRootOfTwentyThree

+0

Esta es la forma en que siempre lo he hecho, y funciona bien. Sospecho que es más portátil que las otras soluciones propuestas anteriormente también. –

12

no estoy seguro si entiendo totalmente el problema, pero supongo que está relacionado con el que tuve, estoy usando Qt con la comunicación de socket TCP, todos los no-bloqueo, Windows y Linux ..

quería llegar una notificación rápida cuando un cliente ya conectado falló o desapareció por completo, y no esperó el predeterminado más de 900 segundos hasta que se levantó la señal de desconexión. El truco para hacer que esto funcionara era establecer la opción de socket TCP_USER_TIMEOUT de la capa SOL_TCP en el valor requerido, expresado en milisegundos.

esta es una opción comparativamente nueva, por favor vea http://tools.ietf.org/html/rfc5482, pero aparentemente está funcionando bien, lo intenté con WinXP, Win7/x64 y Kubuntu 12.04/x64, mi elección de 10 s resultó ser un poco más larga, pero mucho mejor que cualquier otra cosa que haya intentado antes ;-)

el único problema que encontré fue encontrar el adecuado incluye, ya que aparentemente esto no se agrega al socket estándar incluye (todavía ...), así que finalmente los definió mismo de la siguiente manera:

#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 

configurar esta opción toma sólo funciona cuando el cliente ya está conectado, las líneas de código parecerse a:

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

y el fracaso de una conexión inicial es capturado por un temporizador comenzó cuando se llama a connect(), ya que no habrá ninguna señal de Qt para esto, no se planteará la señal de conexión, ya que hay será sin conexión, y también no será planteado la señal de desconexión, ya que no ha habido una conexión aún ..

+0

Esta respuesta me ayudó después de que no funcioné con la configuración KEEPALIVE. ¡Gracias! – sthlm58

+0

Gracias!esto me ayudó a abordar el retraso de 15 minutos en la desconexión causada por el temporizador de retransmisión TCP. – brokenfoot

4

connect tiempo de espera tiene que ser manejado con un zócalo de no bloqueo (GNU LibC documentation en connect). Obtiene connect para devolver inmediatamente y luego usa select para esperar con un tiempo de espera para que se complete la conexión.

Esto también se explica aquí: Operation now in progress error on connect(function) error.

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; 
    } 
} 
+0

¡Gran respuesta! ¿Puedo saber cuál es el valor recomendado para el tiempo de espera aquí, es decir, para el tiempo de espera de conexión? – Coder

-4

Por supuesto, la primera respuesta es la MEJOR. ¿Puedo agregar algo?

...

Después del 2 setsockopt Puede controlar si el cliente pasó la prueba de tiempo de espera, o no con esto:

después de la

n = readline(sockd, recvline, MAXLINE); 

tienes que insertar

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; 
} 
Cuestiones relacionadas