Programación de red Linux (uso de la función epoll)


prefacio

En este artículo, explicamos cómo utilizar la función epoll. En comparación con poll, epoll tiene un rendimiento mejorado.

1. Explicación del concepto y características de epoll.

epoll es un mecanismo de multiplexación de alto rendimiento en Linux que monitorea una gran cantidad de descriptores de archivos y notifica a las aplicaciones cuando están listas. Se optimiza y mejora aún más en función de la selección y la encuesta.

epoll 的主要特点包括:

1. No hay límite en la cantidad de descriptores de archivos: a diferencia de seleccionar y sondear, epoll utiliza un mecanismo de notificación listo basado en eventos sin un límite predefinido en la cantidad de descriptores de archivos, que puede admitir conexiones simultáneas a mayor escala.

2. Notificación de eventos eficiente: epoll utiliza la estructura de datos de eventos compartida por el kernel y el espacio del usuario para registrar el evento del descriptor de archivo en el espacio del kernel. Cuando el evento está listo, el kernel notifica directamente el evento listo al espacio del usuario, evitar cada Cada llamada requiere la sobrecarga de rendimiento de atravesar toda la matriz de descriptores de archivos.

3. Colección de eventos listos separados: epoll copia los eventos listos del espacio del kernel al espacio del usuario para formar una colección de eventos listos separada. Los usuarios pueden atravesar esta colección directamente para procesar eventos listos sin atravesar toda la matriz de descriptores de archivos.

4. Admite activación por flanco y activación horizontal: epoll proporciona dos modos para manejar eventos, uno es el modo de activación por flanco (EPOLLET), que solo notifica a la aplicación cuando cambia el estado, y el otro es el modo de activación horizontal (predeterminado). La aplicación recibe una notificación mientras el evento está listo.

5. Menor sobrecarga de copia de memoria: epoll utiliza tecnología de mapeo de memoria para evitar la sobrecarga de copiar datos de eventos desde el kernel al espacio del usuario para cada llamada, reduciendo así la cantidad de llamadas al sistema y la sobrecarga de copia de memoria.

6. Admite un control de tiempo de espera de mayor precisión: a diferencia de la encuesta, los parámetros de tiempo de espera de epoll están en milisegundos y nanosegundos, lo que proporciona un control de tiempo de espera de mayor precisión.

总体来说,epoll 在性能上相比于 select 和 poll 有较大的优势,特别适用于高并发场景下的网络编程。它的高效事件就绪通知、支持大规模并发连接、较低的内存拷贝开销以及较高的超时精度,使得它成为开发高性能服务器和网络应用的首选机制。

Segundo, mecanismo de implementación de epoll

epoll se implementa utilizando Red-Black Tree. epoll es un mecanismo eficiente de notificación de eventos proporcionado por el sistema operativo Linux, que se utiliza para manejar una gran cantidad de conexiones simultáneas. Puede monitorear los cambios de estado de múltiples descriptores de archivos y realizar el procesamiento correspondiente a través de funciones de devolución de llamada cuando los descriptores de archivos estén listos.

En el kernel de Linux, epoll utiliza un árbol rojo-negro como estructura de datos principal para mantener un conjunto de descriptores de archivos registrados. El árbol rojo-negro es un árbol de búsqueda binario autoequilibrado con una complejidad temporal rápida para operaciones de inserción, eliminación y búsqueda. Al utilizar árboles rojo-negro, epoll puede recuperar y administrar de manera eficiente una gran cantidad de descriptores de archivos.

Cuando ocurre un evento en el descriptor de archivo, epoll localiza rápidamente el nodo correspondiente a través de la operación de búsqueda del árbol rojo-negro y activa la función de devolución de llamada registrada para el procesamiento de eventos. La razón para utilizar un árbol rojo-negro es que puede mantener un buen equilibrio y garantizar que la complejidad temporal en el peor de los casos de las operaciones de búsqueda, inserción y eliminación sea O (log n), lo que garantiza el alto rendimiento y la escalabilidad de epoll. .

En resumen, epoll se implementa utilizando árboles rojo-negro como estructura de datos subyacente, lo que le permite proporcionar un mecanismo eficiente de notificación de eventos cuando se maneja una gran cantidad de conexiones simultáneas.

3. Explicación de las funciones relacionadas con epoll

1.epoll_create función

Prototipo de función:

int epoll_create(int size);

La función epoll_create crea una instancia de epoll y devuelve un descriptor de archivo que identifica la instancia de epoll. El parámetro de tamaño es una pista que indica el límite superior de la cantidad de descriptores de archivos que la instancia de epoll puede monitorear. Pero en la mayoría de los casos, este parámetro se ignora y se puede pasar cualquier valor.

El descriptor de archivo devuelto se puede utilizar para realizar operaciones posteriores en la instancia de epoll, como registrar, modificar y eliminar eventos del descriptor de archivo.

Cabe señalar que la función epoll_create devuelve un número entero no negativo cuando tiene éxito, lo que indica el descriptor de archivo de la instancia de epoll. Si ocurre un error, el valor de retorno es -1 y el código de error errno se establece para indicar el error específico. tipo.

2.función epoll_ctl

Prototipo de función:

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

La función epoll_ctl opera una instancia de epoll específica a través del parámetro epfd especificado. El parámetro op indica el tipo de operación, que puede ser uno de los tres siguientes:

EPOLL_CTL_ADD: agregue el descriptor de archivo especificado fd a la instancia de epoll y registre el evento correspondiente. De esta manera, se notifica a la aplicación cuando un evento en ese descriptor de archivo está listo.

EPOLL_CTL_MOD: Modifica el evento correspondiente al descriptor de archivo fd registrado en la instancia de epoll. Se puede modificar el tipo de evento, eventos de interés, datos de usuario asociados, etc.

EPOLL_CTL_DEL: elimina el descriptor de archivo fd registrado en la instancia de epoll.

El parámetro fd es el descriptor del archivo de destino, que se utiliza para especificar el descriptor de archivo que debe operarse.

El parámetro de evento es un puntero a la estructura struct epoll_event, que se utiliza para especificar la configuración relacionada con el evento. Esta estructura contiene dos miembros:

Eventos uint32_t: indica el tipo de evento registrado, que puede ser EPOLLIN (evento legible), EPOLLOUT (evento grabable), EPOLLRDHUP (el par cierra la conexión), EPOLLPRI (datos de emergencia son legibles), EPOLLERR (evento de error), etc. Se puede combinar usando máscaras de bits.

Datos epoll_data_t: se utilizan para almacenar información de datos del usuario, que puede ser el valor del descriptor del archivo en sí o un puntero de estructura de datos definido por el usuario.

El valor de retorno de la función es 0, lo que significa que la operación fue exitosa y -1, que significa que ocurrió un error. La información específica del error se puede obtener verificando la variable errno.

3.función epoll_wait

Prototipo de función:

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

El parámetro epfd es el descriptor de la instancia de epoll, que especifica la instancia de epoll para esperar eventos.

El parámetro de eventos es una matriz utilizada para almacenar información de eventos. Cada elemento de la matriz es una estructura de tipo struct epoll_event, que se utiliza para almacenar descriptores de archivos listos y la información de eventos correspondiente.

El parámetro maxevents indica el tamaño de la matriz de eventos, es decir, el número máximo de eventos que se pueden esperar.

El parámetro de tiempo de espera es el tiempo de espera en milisegundos. Determina el comportamiento de bloqueo de la función epoll_wait:

Si el tiempo de espera se establece en -1, significa bloquear indefinidamente hasta que ocurra un evento.

Si el tiempo de espera se establece en 0, significa que no hay bloqueo y el evento actualmente listo se devolverá inmediatamente. Si no hay ningún evento listo, se devolverá 0.

Si el tiempo de espera se establece en un entero positivo, significa bloquear y esperar el tiempo especificado antes de regresar. Si el evento no está listo dentro del período de tiempo de espera, se devolverá 0.

La función epoll_wait devuelve el número de descriptores de archivo del evento listo cuando tiene éxito, devuelve -1 si ocurre un error y establece el código de error errno para indicar el tipo de error específico.

Cuarto, epoll implementa un servidor concurrente

当使用epoll实现并发服务器时,通常的步骤包括以下几个主要环节:

1. Crear socket: utilice la función de socket para crear un socket de escucha para aceptar solicitudes de conexión del cliente.

2. Vincular socket: utilice la función de vinculación para vincular el socket de escucha a una dirección IP y un puerto específicos.

3. Escuche las conexiones: utilice la función de escucha para comenzar a escuchar las solicitudes de conexión y especifique la cantidad máxima de conexiones que el servidor puede aceptar.

4. Cree una instancia de epoll: utilice la función epoll_create para crear una instancia de epoll y devolver un descriptor de archivo.

5. Agregue el socket de escucha a la instancia de epoll: use la función epoll_ctl para agregar el socket de escucha a la instancia de epoll y registre la preocupación por el evento de lectura.

6. Ingrese al bucle de eventos: llame a la función epoll_wait en un bucle para esperar a que ocurra el evento. Esta función bloqueará el hilo actual hasta que ocurra un evento. Una vez que ocurre un evento, devolverá una lista de eventos listos.

7. Procesar eventos listos: recorra la lista de eventos listos y procese cada evento. Dependiendo del tipo de evento se pueden realizar operaciones como aceptar conexiones, leer datos, enviar datos o cerrar conexiones.

8. Agregue o elimine descriptores de archivos según sea necesario: después de procesar un evento, puede usar la función epoll_ctl para agregar o eliminar dinámicamente descriptores de archivos según sea necesario para continuar escuchando otros eventos.

9. Repita los pasos 6 a 8: continúe ejecutando los pasos 6 a 8 en un bucle para procesar nuevos eventos listos hasta que el servidor se apague activamente o se produzca una condición de error.

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/epoll.h>

#define MAX_EVENTS  1024 //最多可以等待多少个事件

int main()
{
    
    
    int server = 0;
    struct sockaddr_in saddr = {
    
    0};
    int client = 0;
    struct sockaddr_in caddr = {
    
    0};
    socklen_t asize = 0;
    int len = 0;
    char buf[32] = {
    
    0};
    int maxfd;
    int ret = 0;
    int i = 0;

    server = socket(PF_INET, SOCK_STREAM, 0);

    if( server == -1 )
    {
    
    
        printf("server socket error\n");
        return -1;
    }

    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = htonl(INADDR_ANY);
    saddr.sin_port = htons(8888);

    if( bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1 )
    {
    
    
        printf("server bind error\n");
        return -1;
    }

    if( listen(server, 128) == -1 )
    {
    
    
        printf("server listen error\n");
        return -1;
    }

    printf("server start success\n");


    struct epoll_event event, events[MAX_EVENTS];
    /*创建epoll*/
    int epollInstance = epoll_create1(0);
    if (epollInstance == -1) 
    {
    
    
        printf("Failed to create epoll instance\n");
    }
    /*将服务器添加进入event中*/
    event.events = EPOLLIN;
    event.data.fd = server;

    if (epoll_ctl(epollInstance, EPOLL_CTL_ADD, server, &event) == -1) 
    {
    
    
        printf("Failed to add server socket to epoll instance");
    }        

    while( 1 )
    {
    
            
        int numEventsReady = epoll_wait(epollInstance, events, MAX_EVENTS, -1);
        if (numEventsReady == -1) 
        {
    
    
            printf("Failed to wait for events");
            return -1;
        }

        for(i = 0; i < numEventsReady; i++)
        {
    
    
            if(events[i].data.fd == server)
            {
    
    
                /*有客户端连接上来了*/
                asize = sizeof(caddr);  
                client = accept(server, (struct sockaddr*)&caddr, &asize);
                printf("client is connect\n");

                event.events = EPOLLIN | EPOLLET;
                event.data.fd = client;

                if (epoll_ctl(epollInstance, EPOLL_CTL_ADD, client, &event) == -1) 
                {
    
    
                    printf("Failed to add client socket to epoll instance");
                    return -1;
                }                
            }
            else
            {
    
    
                /*处理客户端的请求*/
                len = read(events[i].data.fd, buf, 1024);
                if(len == 0)
                {
    
    
                    printf("client is disconnect\n");
                    close(events[i].data.fd);
                }
                else
                {
    
    
                    /*对接收到的数据进行处理*/
                    printf("read buf : %s\n", buf);
                    write(events[i].data.fd, buf, len);
                }
            }
        }        


    }
    
    close(server);

    return 0;
}

Resumir

Este artículo termina aquí y el siguiente artículo continúa explicando el conocimiento de la programación de redes Linux.

Supongo que te gusta

Origin blog.csdn.net/m0_49476241/article/details/132376808
Recomendado
Clasificación