Llamada al sistema Select en S.O Linux

Iniciado por SDCC, 9 Marzo 2020, 09:12 AM

0 Miembros y 1 Visitante están viendo este tema.

SDCC

Hola a todos, resulta que tenía la duda de si era necesario ajustar el socket para que fuera no bloqueante antes de suscribirlo a una llamada select. Buscandome me encontre con esto:
https://stackoverflow.com/questions/16628743/should-socket-be-set-non-blocking-before-it-is-polled-by-select

Me queda claro la razón por la cual debe ser no bloqueante pero realmente vale la pena seguir ese consejo, al menos en el caso de conexiones TCP. Es decir:

Yo tengo un select a la espera de que varias conexiones TCP esten listas para ser leidas, me surte el problema de que se me notifica de que el socket esta listo para lectura, entonces prosigo a intentar hacer la lectura con el respectivo método, sin tener conocimiento de que fue un fallo y el contenido fue descartado por algo como que fallo el checksum(situación que menciona la respuesta del link), esto me va ocasionar distintos comportamientos dependiendo si el socket es bloqueante...
1. Si es bloqueante, entonces mi hilo estara bloqueada esperando que realmente la conexión tenga información entrante, pero este tiempo bloqueado creo que es aceptable debido a que es TCP, entonces tengo la certeza de que habrá una retransmisión en un rango de tiempo relativamente corto. Sin embargo se podría presentar el mismo problema de que el siguiente envio tuviera el mismo problema, ¿acaso se tiene la seguridad de que el kernel va ser capaz de quitar la información entrante erronea(que no cumplio con el checksum) antes de que la función recv recolecte los datos?, agregando a esta solución si quisiera asegurar que el tiempo de bloquea no sea mayor a N porción de segundo, podria agregar:
setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof timeout);
Al final podría decidir que si pasa el timeout, entonces vuelva a bloquearme en el select.
Nota. Aquí surge una duda de como es que select se da cuenta que un socket esta listo. Puede ser que lo determine conforme solo a si tiene alguna actividad relacionada con el, sin que esto realmente tenga como consecuencia que algo se este poniendo en la cola de entrada, esto respondería la pregunta de por que se notifica como que el socket esta listo cuando realmente no se ha terminado de confirmar la información entrante.

2. Si no es bloqueantes, entonces es cierto que una llamada recv no me bloquearía pero ¿no tendría que hacer todo un mecanismo para asegurarme de recolectar todo el contendio de esa petición?  a través de algo como recibe hasta que no encuentres el final de la respuesta, mientras no encuentres el final de la respuesta repite:

recv(....);
sleep(algunos_milisegundos);

sin embargo me da la sensación de que se degradaría mas el desempeño de la aplicación por ese sleep, debido a que ese hilo no tendría ninguna otra tarea que hacer, seria algo parecido a una espera ocupada deficiente.

Vi una implementación en python que lo hace es suscribir los sockets como NO_BLOQEUANTES al select, posterior a eso cuando se le notifica que alguno esta listo, lo cambia para que sea BLOQUEANTE pero finalmente creo que se llega al mismo problema, realmente es una condición de carrera que en algunos casos puede resultar en lo mismo que si siempre fuera BLOQUEANTE. ¿Alguna alternativa u opinión de cual seria la solución ideal?

dgrr19

Hola,

TLDR.
No te compliques. Las operaciones que haces en linux sobre sockets son las mismas que se hacen sobre ficheros, es decir, es un descriptor aun asi. La llamada `select` tiene sus propios medios para que pongas el timeout cuando la invocas, no hace falta ponerselo al socket. Ademas, el setsockopt que pones no hace lo que parece que quieres hacer.

No es necesario que uses sockets no bloqueantes. Los sockets no bloqueantes lo unico que hacen es no esperar a que se realiza la operacion, es decir, que si usas `read(socket, buf, size)` en el momento en el que la llamada vaya a bloquear porque tiene que esperar al otro peer entonces retorna un -1 con errno = EAGAIN o EWOULDBLOCK.

Select lo unico que hace es notificarte de que hay un evento activo en alguno de los sockets. Por ejemplo, si el socket del servidor tiene un evento, lo mas probable es que tengas que aceptar nuevas conexiones. Si el evento lo tiene un socket de un cliente, entonces es porque te esta enviando algo, por ejemplo.

A lo mejor tendrias que mirar epoll. Epoll a parte de ser mas rapido, a mi manera de ver las cosas es mas facil de usar.
Eres la media de los cinco que te rodean.

SDCC

#2
Cita de: dgrr19 en 10 Marzo 2020, 20:34 PM
No es necesario que uses sockets no bloqueantes. Los sockets no bloqueantes lo unico que hacen es no esperar a que se realiza la operacion, es decir, que si usas `read(socket, buf, size)` en el momento en el que la llamada vaya a bloquear porque tiene que esperar al otro peer entonces retorna un -1 con errno = EAGAIN o EWOULDBLOCK.

Estoy de acuerdo contigo en que para la funcionalidad normal de mi programa no es necesario usar sockets NO BLOQUEANTES, lo que me genera la duda es lo que dice la respuesta del link que adjunto con el tema. En esa respuesta explica que debido a la implementación en LINUX, usar sockets BLOQUEANTES y select en principio no deberían generar problemas la mayoría de las veces, sin embargo menciona que puede succeder un caso en el que SELECT notifique que un socket esta como READY para leer, pero sin embargo el mensaje que llega puede ser descartado por la proxima comprobación del CHECKSUM, esto haciendo que realmente el recv que supone que no debería ser tan tardado debido a que SELECT nos notifico que estaba listo, bloque finalmente todo el hilo esperando a que realmente llegue una información valida y se cree un pequeño deficit. Claro que si las cosas son así esto puede crear un BUG en casos donde el socket es de tipo DATAGRAM(UDP) debido a que no se tiene la certeza de un mensaje va llegar pronto creando que el hilo solo este esperando un recv de un cliente y este ignorando todos los demas que han sido adjuntados al SELECT.En el caso de TCP se tiene la certeza de que va ser un plazo no tan largo debido a que se detecataria el paquete rechazado y se reenviaria en un plazo de tiempo finito.

Cita de: dgrr19 en 10 Marzo 2020, 20:34 PM
A lo mejor tendrias que mirar epoll. Epoll a parte de ser mas rapido, a mi manera de ver las cosas es mas facil de usar.

He visto un poco sobre el, le voy a dar una checada, gracias.

La verdad no estoy del todo seguro de que esto realmente pueda succeder, o que realmente valga la pena intentarlo evitar pensando en que es muy poco posible que se presente.

RayR

El ejemplo específico de fallo del checksum al que se refieren en el link que pusiste sólo aplicaba para UDP, y hace tiempo fue corregido. Todavía podría presentarse ese problema, por causas distintas, pero no sé qué tan probable sea. Si esto sólo lo estás haciendo para practicar o aprender, perfectamente puedes usar select con sockets bloqueantes, pero ten en cuenta que para usos más serios, ignorar la posibilidad de error y simplemente suponer que casi nunca va a ocurrir es inaceptable, sobre todo cuando no es difícil hacerlo de otras formas, como con sockets no bloqueantes o, como ya te sugirieron, con epoll, que no tiene el problema de select.

Lo único que select hace es preguntar al kernel (enviándole una estructura, que el kernel rellena con información) si los descriptores de archivos especificados están listos. En este caso, "listo" significa que una operación de lectura/escritura no se bloquearía. En otras palabras, select simplemente informa si puedes leer o escribir en los archivos, sockets, etc. especificados sin que haya bloqueo. Que haya o no datos nuevos es algo que sólo sabemos intentando leerlos.

Cita de: SDCC¿acaso se tiene la seguridad de que el kernel va ser capaz de quitar la información entrante erronea(que no cumplio con el checksum) antes de que la función recv recolecte los datos?

Sí. Una aplicación jamás debería leer datos que no pasen la verificación del checksum. Sólo una implementación defectuosa de TCP/IP permitiría algo así. Volviendo al problema inicial del link, lo que sucedía era que, al recibir datagramas, se ponían en la cola, y el checksum únicamente se verificaba en el momento en que una función como recv intentaba leerlos. En caso de fallo, los datos se descartaban y la operación de lectura se bloqueaba (en el caso llamada bloqueante), por lo que, aún en este caso, recv nunca recogería datos que no hubieran pasado el checksum.

En el ejemplo que pones al final, no sería necesario el sleep, ya que select se encarga de eso. En cuanto a asegurarte de recibir el número de bytes deseados, eso es algo que deberías hacer independientemente de si usas sockets bloqueantes o no bloqueantes. No hay garantía de que recv te dé la cantidad de información que le solicites. Incluso con el flag MSG_WAITALL, todavía hay circunstancias en las que podrías necesitar más de una llamada a recv para leer completo el bloque deseado, por lo que lo correcto sería que tu programa maneje esa posibilidad. Claro que, como te decía al principio, todo depende de lo que consideres aceptable en tu programa. Lo importarte es no engañarse creyendo que los errores poco probables no importan. Sí que importan. Simplemente en algunas circunstancias puede ser válido no manejarlos.