studio reales proyectos para libro introducción incluye herramientas fuente español desarrollo código con avanzado aplicaciones c linux sockets messaging file-descriptor

c - reales - libro de android studio en español pdf



¿Puede alguien darme un ejemplo de cómo se alerta a select() cuando un fd está "listo"? (2)

No sé por qué me cuesta mucho encontrar esto, pero estoy viendo un código de Linux en el que estamos utilizando select() esperando a que un descriptor de archivo informe que está listo. Desde la página del manual de select:

select() and pselect() allow a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O operation

Entonces, eso es genial ... Llamo a seleccionar en algún descriptor, le doy un valor de tiempo de espera y comienzo a esperar que la indicación se vaya. ¿Cómo informa el descriptor de archivo (o el propietario del descriptor) que está "listo" para que la declaración select() devuelva?


Desde la misma página de manual:

Al salir, los conjuntos se modifican en su lugar para indicar qué descriptores de archivos realmente cambiaron de estado.

Así que use FD_ISSET() en los conjuntos pasados ​​para seleccionar para determinar qué FDs están listos.


Informa que está listo volviendo.

select espera para eventos que normalmente están fuera del control de su programa. En esencia, al llamar a select , su programa dice "No tengo nada que hacer hasta que ... suspenda mi proceso".

La condición que especifique es un conjunto de eventos, cualquiera de los cuales lo despertará.

Por ejemplo, si está descargando algo, su bucle tendría que esperar a que lleguen nuevos datos, a que se produzca un tiempo de espera si la transferencia se atasca o al usuario a interrumpir, que es precisamente lo que hace la select .

Cuando tiene varias descargas, los datos que llegan a cualquiera de las conexiones activan la actividad en su programa (necesita escribir los datos en el disco), por lo que le daría una lista de todas las conexiones de descarga para select en la lista de descriptores de archivos para ver. para "leer".

Cuando carga datos en algún lugar al mismo tiempo, otra vez usa select para ver si la conexión acepta actualmente datos. Si el otro lado está en el acceso telefónico, reconocerá los datos solo lentamente, por lo que su búfer local de envío siempre estará lleno, y cualquier intento de escribir más datos se bloqueará hasta que el espacio del búfer esté disponible o falle. Al pasar el descriptor de archivo que estamos enviando para select como un descriptor de "escritura", se nos notifica tan pronto como el espacio de búfer está disponible para enviar.

La idea general es que su programa se convierte en impulsado por eventos , es decir, reacciona a eventos externos desde un bucle de mensajes común en lugar de realizar operaciones secuenciales. Le dice al núcleo "este es el conjunto de eventos para los que quiero hacer algo", y el núcleo le ofrece un conjunto de eventos que han ocurrido. Es bastante común que ocurran dos eventos simultáneamente; por ejemplo, un acuse de recibo de TCP se incluyó en un paquete de datos, esto puede hacer que tanto el mismo fd sea legible (los datos están disponibles) como el de escritura (los datos confirmados se han eliminado del búfer de envío), por lo que debe estar preparado para manejar todos los eventos antes de llamar a select nuevo.

Uno de los puntos más importantes es que la select básicamente le da una promesa de que una invocación de read o write no se bloqueará, sin ofrecer ninguna garantía sobre la llamada en sí. Por ejemplo, si hay un byte de espacio disponible en el búfer, puede intentar escribir 10 bytes, y el núcleo volverá y dirá "He escrito 1 byte", por lo que también debe estar preparado para manejar este caso. Un enfoque típico es tener un búfer "datos para escribir en este fd", y siempre que no esté vacío, el fd se agrega al conjunto de escritura, y el evento "grabable" se maneja intentando escribir todo Los datos actualmente en el búfer. Si el búfer está vacío después, está bien, si no, simplemente espere "escribible" nuevamente.

El conjunto "excepcional" rara vez se usa: se usa para protocolos que tienen datos fuera de banda donde es posible que la transferencia de datos se bloquee, mientras que otros datos deben pasar. Si su programa actualmente no puede aceptar datos de un descriptor de archivo "legible" (por ejemplo, está descargando y el disco está lleno), no desea incluir el descriptor en el conjunto "legible", porque no puede manejar el evento y select volverá inmediatamente si se invoca de nuevo. Si el receptor incluye el fd en el conjunto "excepcional", y el remitente solicita a su pila de IP que envíe un paquete con datos "urgentes", el receptor se reactiva y puede decidir descartar los datos no manejados y volver a sincronizar con el remitente. . El protocolo de telnet usa esto, por ejemplo, para el manejo de Ctrl-C. A menos que esté diseñando un protocolo que requiera tal característica, puede omitirlo fácilmente sin ningún daño.

Ejemplo de código obligatorio:

#include <sys/types.h> #include <sys/select.h> #include <unistd.h> #include <stdbool.h> static inline int max(int lhs, int rhs) { if(lhs > rhs) return lhs; else return rhs; } void copy(int from, int to) { char buffer[10]; int readp = 0; int writep = 0; bool eof = false; for(;;) { fd_set readfds, writefds; FD_ZERO(&readfds); FD_ZERO(&writefds); int ravail, wavail; if(readp < writep) { ravail = writep - readp - 1; wavail = sizeof buffer - writep; } else { ravail = sizeof buffer - readp; wavail = readp - writep; } if(!eof && ravail) FD_SET(from, &readfds); if(wavail) FD_SET(to, &writefds); else if(eof) break; int rc = select(max(from,to)+1, &readfds, &writefds, NULL, NULL); if(rc == -1) break; if(FD_ISSET(from, &readfds)) { ssize_t nread = read(from, &buffer[readp], ravail); if(nread < 1) eof = true; readp = readp + nread; } if(FD_ISSET(to, &writefds)) { ssize_t nwritten = write(to, &buffer[writep], wavail); if(nwritten < 1) break; writep = writep + nwritten; } if(readp == sizeof buffer && writep != 0) readp = 0; if(writep == sizeof buffer) writep = 0; } }

Intentamos leer si tenemos espacio disponible en el búfer y no hubo ningún fin de archivo o error en el lado de lectura, e intentamos escribir si tenemos datos en el búfer; Si se alcanza el final del archivo y el búfer está vacío, entonces hemos terminado.

Este código se comportará claramente como subóptimo (es un código de ejemplo), pero debería poder ver que es aceptable que el kernel haga menos de lo que pedimos tanto en lecturas como en escrituras, en cuyo caso simplemente regresamos y decimos "siempre que estás listo ", y que nunca leemos ni escribimos sin preguntar si se bloqueará.