2009-01-19 9 views
8

Estoy trabajando en un sistema Linux (servidor Ubuntu 7.04 con kernel 2.6.20).seleccionar en el socket UDP no termina cuando se cierra el socket - ¿Qué estoy haciendo mal?

Tengo un programa que tiene un hilo (thread1) esperando en una selección para que un socket UDP sea legible. Estoy usando la selección (con mi socket como lectura única y el único exceptofd) en lugar de simplemente llamar a recvfrom porque quiero un tiempo de espera.

De otro hilo, apago y cierro el socket. Si hago esto mientras thread1 está bloqueado en un recvfrom, entonces el recvfrom terminará inmediatamente. Si hago esto mientras thread1 está bloqueado en una selección con un tiempo de espera, la selección NO terminará inmediatamente, pero eventualmente terminará correctamente.

¿Alguien puede decirme por qué la selección no se cierra tan pronto como se cierra la toma? ¿No es eso una excepción? Puedo ver que no es legible (obviamente), pero está cerrado, lo que parece ser excepcional.

Aquí está la apertura de la toma (todo el manejo de errores retira para mantener las cosas simples):

m_sockfd = socket(PF_INET, SOCK_DGRAM, 0); 
struct sockaddr_in si_me; 
memset((char *) &si_me, 0, sizeof(si_me)); 
si_me.sin_family = AF_INET; 
si_me.sin_port = htons(port); 
si_me.sin_addr.s_addr = htonl(INADDR_ANY); 
if (bind(m_sockfd, (struct sockaddr *)(&si_me), sizeof(si_me)) < 0) 
{ 
// deal with error 
} 

Aquí está la instrucción select que Thread1 ejecuta:

struct timeval to; 
to.tv_sec = timeout_ms/1000;// just the seconds portion 
to.tv_usec = (timeout_ms%1000)*1000;// just the milliseconds 
            // converted to microseconds 

// watch our one fd for readability or 
// exceptions. 
fd_set readfds, exceptfds; 
FD_ZERO(&readfds); 
FD_SET(m_sockfd, &readfds); 
FD_ZERO(&exceptfds); 
FD_SET(m_sockfd, &exceptfds); 

int nsel = select(m_sockfd+1, &readfds, NULL, &exceptfds, &to); 

ACTUALIZACIÓN: Es evidente que (como se indica a continuación), cerrar el zócalo no es una condición excepcional (desde el punto de vista de selección). Creo que lo que necesito saber es: ¿Por qué? Y, es eso intencional ?.

REALMENTE quiero entender el pensamiento detrás de este comportamiento selecto porque parece contrario a mis expectativas. Por lo tanto, obviamente necesito ajustar mi pensamiento sobre cómo funciona la pila de TCP. Por favor, explícamelo.

Respuesta

4

Quizás deba usar algo más para activar la selección. Tal vez una pipa o algo así.

+0

Esa sería una buena solución. Haga que la selección espere tanto en el zócalo como en la tubería, y el otro hilo escribiría en la tubería para hacer que la selección regrese. –

+0

Eso funcionaría y probablemente sea con lo que termine. Solo quería evitar cualquier parte móvil adicional. ¡Gracias! –

4

UDP es un protocolo sin conexión. Como no hay conexión, ninguna se puede romper, por lo que el consumidor no sabe que el productor nunca volverá a enviar.

Puede hacer que el productor envíe un mensaje de "final de la secuencia" y que el consumidor finalice al recibirlo.

+1

Todas son correctas, verdaderas y una solución viable, pero: El RECEPTO termina cuando cierro el socket. ¿Por qué no selecciona? Estoy realmente confundido sobre por qué cerrar el socket no produce una excepción que finaliza la selección. –

+0

Creo que malinterpretó la pregunta: los dos hilos no están manipulando dos sockets UDP. Hay ** solo un socket UDP **. – curiousguy

2

Creo que la solución más obvia es que estar cerrado no se considera una condición excepcional. Creo que la raíz del problema es que realmente no estás adoptando la filosofía select. ¿Por qué diablos estás jugueteando con el socket en otro hilo, eso suena como una receta para el desastre.

+0

Lo único que hago con el socket de otro hilo es cerrarlo, para detener una espera en progreso. De esta forma puedo hacer que mi programa salga ANTES de que expire el tiempo de espera de selección. –

2

Diría que la diferencia radica en que recvfrom intenta leer activamente un mensaje desde un único socket, donde select está esperando que llegue un mensaje, posiblemente en múltiples identificadores, y no necesariamente en identificadores de socket.

3

¿No podría enviar una señal (por ejemplo, USR2) al subproceso que ocasionaría que select() regresara con EINTR? Luego, en el manejador de señal, establezca un indicador indicándole que no reinicie select()?

Eso eliminaría la necesidad de esperar en múltiples descriptores de archivos, y parece mucho más limpio que usar una tubería para matarlo.

+0

Sí, eso funcionaría, aunque en este caso particular no creo que pueda usar señales internamente (algunas de las clases base mal escritas en nuestra biblioteca usan y/o bloquean señales de maneras extrañas). Si puedo limpiar las bibliotecas subyacentes, podría intentar esto en algún momento. –

0

Su código está fundamentalmente roto.Las variaciones en este error son comunes y han causado errores serios con implicaciones de seguridad masivas en el pasado. Esto es lo que se está perdiendo:

Cuando vaya a cerrar el zócalo, simplemente no hay forma posible de saber si el otro hilo está bloqueado en select o está a punto de bloquearse en select. Por ejemplo, considere lo siguiente:

  1. El hilo va a llamar select, pero no obtener los informes programados.
  2. Cierre el socket.
  3. En un hilo que su código desconoce (tal vez es parte de la administración interna de la memoria de la plataforma o de las funciones internas de registro) una biblioteca abre un socket y obtiene el mismo identificador que el socket que cerró.
  4. El hilo ahora entra en select, pero es select ing en el socket abierto por la biblioteca.
  5. Ataques por desastre.

No debe intentar liberar un recurso mientras otro hilo está, o podría estarlo, usándolo.

Cuestiones relacionadas