Red de conocimiento informático - Conocimiento informático - ¿Cómo hacer que epoll supervise el tipo de evento?

¿Cómo hacer que epoll supervise el tipo de evento?

La interfaz de Epoll es muy simple y tiene tres funciones:

1.int epoll _ create(int size);

Crea un identificador de epoll para indicarle el tamaño. Sobre el monitor. ¿Qué tan grande es la cantidad? Este parámetro es diferente del primer parámetro en select(), que proporciona el valor de fd 1 para una escucha máxima. Cabe señalar que cuando se crea el identificador epoll, ocupará un valor fd. Si observa /proc/process id/fd/ en Linux, puede ver este fd, por lo que debe llamar a close() para cerrarlo después de usar epoll; de lo contrario, el 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 a monitorear.

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

El segundo parámetro representa la operación, representado por tres macros:

EPOLL_CTL_ADD: en epfd Registro un nuevo FD en dfpe;

EPOLL_CTL_MOD: modifica el evento de monitoreo del fd registrado;

EPOLL_CTL_DEL: elimina FD de dfpe;

El tercer parámetro es El fd para ser monitoreado.

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

Estructura epoll_event {

__uint32_t event;

epoll_data_t datos;

};

typedef union epoll_data {

void * ptr

int fd

_ _ uint32 _ t u32

_ _ uint64 _ t u64

} epoll _ data _ t;

El evento puede ser una colección de las siguientes macros:

EPOLLIN: Indica que el descriptor de archivo correspondiente se puede leer (incluido el cierre normal del socket opuesto);

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

EPOLLPRI: Indica que el descriptor de archivo correspondiente tiene datos urgentes que deben leerse (esto debería indicar que han llegado datos fuera de banda);

EPOLLERR: Indica que el descriptor de archivo correspondiente tiene un error;

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

EPOLLET: establece EPOLL en modo de disparo de borde (el valor predeterminado es el disparo horizontal), que es relativo al disparador de nivel.

EPOLLONESHOT: Escucha los eventos una sola vez. Después de escuchar este evento, si necesita continuar escuchando este socket, debe agregar este socket a la cola EPOLL nuevamente.

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

Esperar a que ocurran eventos. Los eventos de parámetros se utilizan para obtener el conjunto de eventos del kernel. maxevents le dice al kernel qué tan grande es este evento. El valor de este maxevents no puede ser mayor que el tamaño al crear epoll_create(). El parámetro timeout es el tiempo de espera (milisegundos, 0 volverá inmediatamente, -1 no está seguro, algunas personas dicen que está bloqueado permanentemente). Esta función devuelve el número de eventos que deben procesarse. Si devuelve 0, significa que se agotó el tiempo de espera.

4. El evento EPOLL tiene dos modos:

El borde activado por borde (ET) solo se activa cuando llegan datos, independientemente de si hay datos en el búfer.

Disparador horizontal (LT) El disparador horizontal se activará mientras haya datos.

Si existe tal ejemplo:

1. Agregamos un identificador de archivo (RFD) 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), devolverá RFD, indicando que está listo para operaciones de lectura.

4. Luego leemos datos de 1 KB.

5. Llame a epoll_wait(2)...

Modo de trabajo del disparador de borde:

Si agregamos RFD a epoll en el descriptor del paso 1 usando el indicador EPOLLET , puede bloquearse después de llamar a epoll_wait(2) en el paso 5 porque los datos restantes todavía están presentes en el búfer de entrada del archivo y el remitente de datos todavía está esperando el mensaje de retroalimentación de datos enviado. El modo operativo ET solo informa eventos cuando ocurren en un identificador de archivo monitoreado. Por lo tanto, en el paso 5, la persona que llama puede dejar de esperar a que los datos restantes aún estén presentes en el búfer de entrada del archivo. En el ejemplo anterior, debido a que la operación de escritura se realizó en el paso 2, se generará un evento en el identificador RFD, que luego 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 es seguro si nos colgaremos después de llamar a epoll_wait(2) en el paso 5. Cuando epoll funciona en modo ET, se deben usar sockets de Windows sin bloqueo para evitar que se le reduzca la tarea de manejar múltiples descriptores de archivos debido al bloqueo de las operaciones de lectura/escritura en el identificador del 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.

Me baso en identificadores de archivos sin bloqueo.

Cuando read(2) o write(2) devuelve OTRA VEZ, solo necesito colgar y esperar. Sin embargo, esto no significa que deba leerse un bucle cada vez que se genera read(), y el manejo de eventos no se considera completo hasta que se genera EAGAIN. Cuando la longitud de los datos leídos devueltos por read () es menor que la longitud de los datos solicitados, se puede determinar que no hay datos en el búfer en este momento y se puede considerar que la lectura del evento ha sido procesada. El disparo horizontal funciona en el modo opuesto. Cuando se llama a la interfaz epoll en modo LT, es equivalente a una encuesta relativamente rápida (2) Independientemente de si se utilizan datos posteriores, sus funciones son las mismas. Porque incluso si usa el modo ET epoll, se generarán múltiples eventos cuando se reciban múltiples bloques de datos. La persona que llama puede configurar el indicador EPOLLONESHOT. Después de que epoll_wait(2) reciba un evento, el identificador de archivo relacionado con el evento se desactivará del descriptor de epoll. Por lo tanto, cuando se configura EPOLLONESHOT, la persona que llama debe usar epoll_ctl(2) con el indicador EPOLL_CTL_MOD para manejar el identificador del archivo.

Luego explique ET, LT:

LT (activado por nivel) es el modo de trabajo predeterminado y admite sockets con y sin bloqueo. De esta manera, el kernel le indica si un descriptor de archivo está listo y luego puede preparar IO para FD. Si no hace nada, el kernel continuará notificándole, por lo que es menos probable que se produzcan errores de programación en este modo. La selección/encuesta tradicional es el representante de este modo.

ET (activado por flanco) es un modo de trabajo de alta velocidad que 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 haga que el descriptor de archivo ya no esté listo (por ejemplo, envíe, reciba o se recibió una solicitud). , o se envió y recibió menos de una determinada cantidad de datos, lo que provocará un error EWOULDBLOCK). Sin embargo, tenga en cuenta que si fd no ha sido una operación IO (lo que hace que no esté listo nuevamente), el kernel no lo enviará más de una vez. Sin embargo, en el protocolo TCP, el efecto de aceleración del modo ET aún requiere más pruebas. confirmación.

En muchas pruebas, veremos que epoll no es mucho más eficiente que select/poll cuando no hay una gran cantidad de conexiones inactivas o inactivas, pero sí cuando encontramos una gran cantidad de conexiones inactivas ( como entornos WAN Al conectar una gran cantidad de conexiones lentas), encontrará que epoll es mucho más eficiente que select/poll. (No probado)

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

while(rs) {

buf len = recv( eventos activos[I]. datos . FD, buf, sizeof(buf), 0);

if(buf len lt; 0) {

//Debido a que es un modo sin bloqueo, cuando errno es EAGAIN, significa que no hay datos para leer en el búfer actual.

//Aquí es donde se manejan los eventos.

if(errno == EAGAIN) {

Romper

} En caso contrario {

Regresar

}

} else if(buflen == 0) {

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

}

if(buflen == sizeof(buf)) {

RS = 1; //Necesito leer de nuevo

} En caso contrario {

RS = 0;

}

}

Además, si el tráfico del remitente es mayor que el tráfico del receptor ( Esto significa que el programa donde se encuentra epoll lee más rápido que el socket reenviado). Debido a que es un socket sin bloqueo, la función send () regresa, pero los datos reales del búfer no se envían al receptor. Si el búfer se lee y envía continuamente, se producirá un error EAGAIN cuando el búfer esté lleno (consulte envío manual). Además, ignore los datos enviados por esta solicitud. Entonces necesitamos encapsular la función socket_send() para lidiar con esta situación. Esta función intentará terminar de escribir los datos antes de regresar, devolviendo -1 para indicar un error. Dentro de socket_send(), cuando el búfer de escritura está lleno (send() devuelve -1, errno es e-again), esperará y volverá a intentarlo. Este método no es perfecto en teoría, puede bloquearse dentro de socket_send() durante mucho tiempo, pero actualmente no existe una mejor manera.

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

ssize _ t tmp

tamaño _ t total = buflen

const char * p = buffer

mientras(1) {

tmp = enviar(sockfd, p, total,

if(); tmp lt; 0) {

send recibe la señal y puede continuar escribiendo, pero aquí se devuelve -1.

if(errno == EINTR)

return-1;

//Si este error se devuelve cuando el socket no es bloqueante, significa escritura buffering La cola de la zona está llena.

//Retrasa aquí y vuelve a intentarlo.

if(errno == EAGAIN) {

us LEEP(1000

Continuar;

}

retorno-1;

}

if((size_t)tmp == total)

Retorno buflen

total-= tmp;

p = tmp;

}

Devolver tmp

}