¿Cómo establecer el tiempo de espera del socket en C al hacer conexiones múltiples?

Estoy escribiendo un progtwig simple que hace conexiones múltiples a diferentes servidores para verificar el 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 ().

Funcionó muy bien, hasta que mi cliente se quejó de que el tiempo de espera es demasiado largo antes de que puedan obtener el informe de error cuando los servidores de destino dejaron 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?

Puede usar las opciones de socket SO_RCVTIMEO y SO_SNDTIMEO para establecer tiempos de espera para cualquier operación de socket, de esta forma:

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: desde la página man de setsockopt :

SO_SNDTIMEO es una opción para establecer 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.

No estoy seguro si entiendo completamente el problema, pero supongo que está relacionado con el que tenía, estoy usando Qt con la comunicación de socket TCP, todo no bloquea, tanto Windows como Linux.

quería recibir una notificación rápida cuando un cliente ya conectado fallaba o desaparecía por completo, y no esperaba el predeterminado más de 900 segundos hasta que se activaba 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 largo, pero mucho mejor que cualquier otra cosa que haya probado antes 😉

el único problema con el que me encontré fue encontrar las inclusiones adecuadas, ya que aparentemente esto no se ha agregado al socket estándar incluye (todavía …), así que finalmente las definí yo mismo de la siguiente manera:

 #ifdef WIN32 #include  #else #include  #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 de socket solo funciona cuando el cliente ya está conectado, las líneas de código se ven así:

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

y la falla de una conexión inicial es capturada por un temporizador iniciado al llamar a connect (), ya que no habrá señal de Qt para esto, la señal de conexión no se activará, ya que no habrá conexión, y la señal de desconexión se Tampoco se plantearán, ya que aún no ha habido una conexión.

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

Mantenga una lista ordenada, o mejor aún, un montón de prioridad como sugiere Heath, 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 ser cerrar un socket que aún no se ha conectado.

connect tiempo de espera de connect debe manejarse con un socket que no sea de locking ( documentación GNU LibC on connect ). Usted obtiene la connect para regresar 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í: Error de operación ahora en progreso en el error de conexión (función) .

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

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

Después de 2 setsockopt Puede controlar si el cliente aprobó la prueba de tiempo de espera o si falló 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; }