Red de conocimiento informático - Material del sitio web - Cómo usar epoll para monitorear cambios de archivos

Cómo usar epoll para monitorear cambios de archivos

La interfaz de epoll es muy simple, con solo tres funciones:

1.int epoll_create(int size);

Crea un identificador de epoll, el tamaño es. Se utiliza para indicarle al núcleo qué tan grande es el número de monitores. Este parámetro es diferente del primer parámetro en select() y proporciona el valor del fd 1 máximo monitoreado. Cabe señalar que después de crear el identificador de epoll, ocupará un valor fd. Si observa /proc/process id/fd/ en Linux, puede ver este fd, por lo que después de usar epoll, debe llamar a close(. ) para cerrar; de lo contrario, fd puede agotarse.

2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

La función de registro de eventos de epoll es registrar el tipo de evento que se va a monitorear.

El primer parámetro es el valor de retorno de epoll_create(),

El segundo parámetro representa la acción, representada por tres macros:

EPOLL_CTL_ADD: registro Agregar el nuevo fd a epfd;

EPOLL_CTL_MOD: Modifica el evento de escucha del fd registrado;

EPOLL_CTL_DEL: Elimina un fd de epfd;

Tercero El primer parámetro es el fd que necesita ser monitoreado.

El cuarto parámetro le dice al kernel qué necesita ser monitoreado. La estructura de struct epoll_event es la siguiente:

struct epoll_event {

.

__uint32_t eventos;

epoll_data_t datos;

};

typedef unión epoll_data {

void *ptr; >

int fd;

__uint32_t u32;

__uint64_t u64;

} epoll_data_t;

los eventos pueden ser una colección de las siguientes macros:

EPOLLIN: Indica que el descriptor de archivo correspondiente se puede leer (incluido el cierre normal del par SOCKET

EPOLLOUT: Indica que el descriptor de archivo correspondiente se puede leer); escrito

EPOLLPRI: Indica que el descriptor de archivo correspondiente tiene datos urgentes para leer (esto debería indicar la llegada de datos fuera de banda);

EPOLLERR: Indica que se ha producido un error. ocurre en el descriptor de archivo correspondiente;

EPOLLHUP: indica que el descriptor de archivo correspondiente está colgado;

EPOLLET: establece EPOLL en el modo de disparo por flanco (activado por borde) (el valor predeterminado es el disparador horizontal ), que es relativo al disparador de nivel (Level Triggered) Para decir.

EPOLLONESHOT: solo escucha el evento una vez. Después de escuchar este evento, si aún necesita continuar monitoreando el socket, debe agregar el socket a la cola EPOLL nuevamente.

3. int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

Espere a que ocurra el evento.

El parámetro events se utiliza para obtener una colección de eventos del kernel. maxevents le dice al kernel qué tan grandes son los eventos. El valor de maxevents no puede ser mayor que el tamaño al crear epoll_create(). El tiempo de espera del parámetro es el tiempo de espera (milisegundos). 0 volverá inmediatamente, -1 volverá. No estoy seguro, algunos dicen que está bloqueado permanentemente). Esta función devuelve la cantidad de eventos que deben procesarse. Si devuelve 0, significa que se agotó el tiempo de espera.

4. Hay dos modelos de eventos EPOLL:

Disparador de borde (ET) El disparador de borde se activa solo cuando llegan datos, independientemente de si todavía hay datos en el búfer.

Disparador por nivel (LT) El disparador por nivel se activará siempre que haya datos.

Supongamos que existe un ejemplo de este tipo:

1. Hemos agregado un identificador de archivo (RFD) utilizado para leer datos de la tubería al descriptor de epoll

2. En este momento, se escriben 2 KB de datos desde el otro extremo de la tubería

3. Llame a epoll_wait(2) y devolverá RFD, lo que indica que está listo para la operación de lectura

p >

4. Luego leemos 1 KB de datos

5. Llama a epoll_wait(2)...

Modo de trabajo activado por borde:

Si Usamos el indicador EPOLLET cuando agregamos el RFD al descriptor de epoll en el paso 1, es posible bloquear después de llamar a epoll_wait(2) en el paso 5 porque los datos restantes aún existen en el búfer de entrada del archivo y el final del envío de datos aún está esperando un mensaje de respuesta de los datos que se han enviado. El modo de trabajo ET informará eventos solo cuando ocurra un evento en el identificador del archivo monitoreado. Por lo tanto, en el paso 5, la persona que llama puede dejar de esperar los datos restantes que aún existen en el búfer de entrada del archivo. En el ejemplo anterior, se generará un evento en el identificador RFD porque se realizó una operación de escritura en el paso 2 y luego el evento se destruirá en el paso 3. Debido a que la operación de lectura en el paso 4 no leyó los datos en el búfer de entrada del archivo vacío, no está claro si se bloqueará después de llamar a epoll_wait(2) en el paso 5. Cuando epoll funciona en modo ET, debe usar un socket sin bloqueo para evitar privar a la tarea de procesar múltiples descriptores de archivos debido al bloqueo de las operaciones de lectura/escritura de un identificador de archivo. Es mejor llamar a la interfaz epoll del modo ET de la siguiente manera, que se presentará más adelante para evitar posibles defectos.

i Basado en identificadores de archivos sin bloqueo

ii Solo cuando read(2) o write(2) devuelve EAGAIN, debe colgar y esperar. Pero esto no significa que deba leer en un bucle cada vez que lea (). El procesamiento del evento no se considera completado hasta que se genera un EAGAIN cuando la longitud de los datos leídos devueltos por read () es menor que la solicitada. longitud de los datos, puede Se determina que no hay datos en el búfer en este momento y se puede considerar que el evento de lectura ha sido procesado. A diferencia del modo de trabajo activado por nivel, cuando se llama a la interfaz epoll en modo LT, es equivalente a una encuesta más rápida (2), e independientemente de si se utilizan los datos posteriores, tienen la misma función. Porque incluso si usa el modo ET epoll, se seguirán generando múltiples eventos al recibir datos de múltiples fragmentos. La persona que llama puede configurar el indicador EPOLLONESHOT Después de que epoll_wait(2) reciba el evento, epoll deshabilitará el identificador de archivo asociado con el evento desde el descriptor de epoll. Por lo tanto, cuando se configura EPOLLONESHOT, usar epoll_ctl(2) con el indicador EPOLL_CTL_MOD para manejar el identificador del archivo se convierte en lo que debe hacer la persona que llama.

Luego explique ET y LT en detalle:

LT (activado por nivel) es el modo de trabajo predeterminado y admite sockets en bloque y sin bloques. En este enfoque, el núcleo indica si un. El descriptor de archivo está listo, entonces puede realizar operaciones de IO en el fd listo. Si no haces nada, el kernel seguirá notificándote, por lo que la posibilidad de errores de programación en este modo es menor. La selección/encuesta tradicional son representantes de este modelo.

ET (activado por flanco) es un modo de trabajo de alta velocidad y solo admite enchufes sin bloqueo. En este modo, el kernel le avisa mediante epoll cuando un descriptor cambia de no listo a listo. Luego asumirá que usted sabe que el descriptor de archivo está listo y no enviará más notificaciones de preparación para ese descriptor de archivo hasta que haga algo que provoque que ese descriptor de archivo ya no esté listo (por ejemplo, se produce un error EWOULDBLOCK cuando enviar, recibir o recibir una solicitud, o cuando enviar o recibir menos de una cierta cantidad de datos). Sin embargo, tenga en cuenta que si no hay ninguna operación IO en este fd (lo que hace que no esté listo nuevamente), el kernel no enviará más notificaciones (solo una vez). Sin embargo, en el protocolo TCP, el efecto de aceleración del modo ET. Todavía es necesario actualizar varias confirmaciones de referencia (no entiendo esta oración).

En muchas pruebas veremos que si no hay una gran cantidad de conexiones inactivas o inactivas, la eficiencia de epoll no será mucho mayor que select/poll, pero cuando encontramos una gran cantidad de Conexiones inactivas (como el entorno WAN (hay una gran cantidad de conexiones lentas), encontrará que la eficiencia de epoll es mucho mayor que la de select/poll.

(No probado)

Además, cuando se utiliza el modelo ET de epoll para funcionar, cuando se genera un evento EPOLLIN, lo que se debe considerar al leer datos es si el tamaño devuelto por recv() es igual al tamaño solicitado, entonces es muy probable que todavía haya datos en el búfer que no se hayan leído, lo que significa que el evento aún no se ha procesado, por lo que es necesario leerlo nuevamente:

mientras (rs) {

buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);

if(buflen lt; 0) {

// Dado que es un modo sin bloqueo, cuando errno es EAGAIN, significa que no hay datos para leer en el buffer actual

// Esto se considera como el evento ha sido procesado.

if(errno == EAGAIN) {

break

} else {

return

}

} else if(buflen == 0) {

// Esto indica que el socket del par se ha cerrado normalmente.

}

if(buflen == sizeof(buf)) {

rs = 1; // Necesito leer de nuevo

} else {

rs = 0;

}

}

Además, si el tráfico del remitente es mayor que el tráfico del receptor (lo que significa que el programa donde se encuentra epoll se lee más rápido que el socket reenviado), porque es un socket sin bloqueo. Entonces, aunque la función send() regresa, los datos reales del búfer no se envían al extremo receptor con lectura y envío continuos. Se producirá un error EAGAIN cuando el búfer esté lleno (consulte el envío manual). Al mismo tiempo, ignore esta solicitud para enviar datos. Por lo tanto, la función que encapsula socket_send () debe usarse para manejar esta situación. intente escribir los datos antes de regresar. Devolver -1 indica un error.

Dentro de socket_send(), cuando el búfer de escritura está lleno (send() devuelve -1 y errno es EAGAIN), esperará y volverá a intentarlo. Este método no es perfecto y, en teoría, puede bloquearse durante mucho tiempo. pero no hay mejor manera todavía.

ssize_t socket_send(int sockfd, const char* buffer, size_t buflen) {

ssize_t tmp;

size_t total = buflen;

const char *p = buffer;

while(1) {

tmp = enviar(sockfd, p, total, 0);

if(tmp lt; 0) {

// Cuando send recibe la señal, puedes continuar escribiendo, pero aquí se devuelve -1.

if(errno == EINTR)

return -1;

// Cuando el socket no es bloqueante, si se devuelve este error, significa que la cola del búfer de escritura está llena,

// Retrasa aquí y vuelve a intentarlo.

if(errno == EAGAIN) {

usleep(1000);

continue; /p>

}

return -1

}

if((size_t)tmp == total)

return buflen;

total -= tmp;

p = tmp;

}

return tmp;

}