GNU/Linux >> Tutoriales Linux >  >> Linux

¿Cómo se alerta a select () de que un fd está listo?

Informa que está listo por regresando.

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

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

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

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

Cuando carga datos en algún lugar al mismo tiempo, vuelve a usar select para ver si la conexión actualmente acepta datos. Si el otro lado está en acceso telefónico, reconocerá los datos lentamente, por lo que su búfer de envío local siempre está lleno, y cualquier intento de escribir más datos se bloqueará hasta que haya espacio disponible en el búfer, o fallará. Al pasar el descriptor de archivo que estamos enviando a select como descriptor de "escritura", recibimos una notificación tan pronto como haya espacio de búfer disponible para enviar.

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

Uno de los puntos más finos es que select básicamente te da la promesa de que una invocación de read o write no bloqueará, sin dar ninguna garantía sobre la llamada en sí. Por ejemplo, si hay disponible un byte de espacio de 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 que se escribirán en este fd", y siempre que no esté vacío, el fd se agrega al conjunto de escritura y el evento "escribible" se maneja intentando escribir todos los datos actualmente en el búfer. Si el búfer está vacío después, está bien, si no, simplemente espere a "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 regresaría inmediatamente si se invocara de nuevo. Si el receptor incluye el fd en el conjunto "excepcional" y el remitente le pide a su pila de IP que envíe un paquete con datos "urgentes", el receptor se despierta y puede decidir descartar los datos no manejados y resincronizar con el remitente. . El telnet El protocolo usa esto, por ejemplo, para el manejo de Ctrl-C. A menos que esté diseñando un protocolo que requiera tal función, puede omitirla 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 de búfer disponible y no hubo final de archivo o error en el lado de lectura, e intentamos escribir si tenemos datos en el búfer; si se llega al final del archivo y el búfer está vacío, entonces hemos terminado.

Este código se comportará claramente de manera subóptima (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 o escribimos sin preguntar si se bloqueará.


Desde la misma página man:

Al salir, los conjuntos se modifican para indicar qué descriptores de archivo cambiaron realmente de estado.

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


Linux
  1. ¿Cómo limitan las canalizaciones el uso de memoria?

  2. Cómo acceder a phpMyAdmin

  3. Cómo prevenir un ataque cibernético

  4. ¿Cómo instalo Duplicity en Ubuntu?

  5. Cómo generar datos de netflow en linux

Cómo iniciar sesión en Plesk

PHP MySQL Seleccionar datos

Cómo fusionar datos en Linux

Cómo agregar una fuente de datos a Redash

¿Cómo instalar ISPConfig en CentOS 7.x?

Cómo eliminar un archivo de datos de AWStats