[TCP/IP] Uso de tecnología de multiplexación de E/S para implementar servidores concurrentes - epoll

Tabla de contenido

Desventajas de seleccionar

función epoll

epoll_create

epoll_ctl

epoll_espera

Implementación del servidor Echo basado en epoll


Desventajas de seleccionar

        Anteriormente, usamos la función de selección para completar la reutilización de la E/S del lado del servidor de eco, pero todavía hay fallas en el código, que se centran principalmente en:

  • Cada vez que llama a la función de selección, debe pasar la información del objeto de monitoreo a la función.
  • Después de llamar a select, diseñe una declaración de bucle en fd_set para monitorear todas las variables del objeto

       Estas dos operaciones tienen una gran pérdida de rendimiento. Y cabe señalar que los sockets son administrados por el sistema operativo. En escenarios donde es necesario llamar a la función de selección con frecuencia, la función de selección requiere que se le pase el objeto de monitoreo (socket), lo que consumirá una gran cantidad de tiempo y espacio.

        Sin embargo, este problema se puede optimizar mediante estrategias apropiadas: pasar el descriptor del archivo del objeto de monitoreo al sistema operativo solo una vez y solo notificar los eventos de interés cuando el objeto de monitoreo cambia.

        En Linux, epoll puede admitir esta operación.

función epoll

*Los kernels de Linux anteriores a la versión 2.5.44 no pueden utilizar la función epoll

        Al implementar epoll, es necesario utilizar otras funciones y estructuras asociadas:

  • epoll_create: crea un espacio para guardar el descriptor del archivo epoll.
  • epoll_ctl: registra y cancela el registro de descriptores de archivos con el espacio.
  • epoll_wait: similar a la función de selección, espere a que cambie el descriptor del archivo.

        En comparación con el uso de la variable fd_set para almacenar el descriptor del archivo del objeto de monitoreo en el método de selección, epoll usa la función epoll_create para solicitar al sistema operativo que guarde el descriptor del archivo del objeto.

Las funciones macroFD_SET FD_CLR          deben usarse en el método de selección  , mientras que en el método epoll, solo necesita  solicitar al sistema operativo que complete  a través de la función epoll_ctl  .

        En términos de monitorear cambios en los descriptores de archivos, epoll llama a la función epoll_wait para monitorear los cambios en los descriptores de archivos y, al mismo tiempo, usa el tipo de estructura epoll_event para reunir los descriptores de archivos modificados.

        El tipo de estructura epoll_event se define de la siguiente manera:

typedef union epoll_data
{
  void *ptr;
  int fd;
  uint32_t u32;
  uint64_t u64;
} epoll_data_t;

struct epoll_event
{
  uint32_t events;	/* 欲监视的 Epoll 事件 */
  epoll_data_t data;	/* 用户数据变量 */
}

        Después de declarar la estructura epoll_event, pásela a la función epoll_wait y los descriptores de archivos modificados se completarán en la estructura, sin la necesidad de usar un bucle para escanear y monitorear todos los descriptores de archivos en el rango.

epoll_create

        La función epoll_create se utiliza para crear una instancia de epoll . Su archivo de encabezado de referencia y estructura de funciones son los siguientes:

#include <sys/epoll.h>

int epoll_create(int size);

/* 成功时返回 epoll 文件描述符,失败时返回-1。
   
   参数size 用来传递epoll实例的大小 */

        Cabe señalar que el valor del parámetro de tamaño en la función no controla el tamaño real de la instancia de epoll. Este valor solo es responsable de indicarle al sistema operativo el valor recomendado para el tamaño de la instancia como referencia para el sistema operativo.

        La instancia creada por la función epoll_create es esencialmente un socket, por lo que también se devolverá el descriptor del archivo. Al mismo tiempo, cuando se complete la operación en el socket, se debe llamar a la función de cierre para cerrarlo.

expandir:

        Las versiones del kernel posteriores a Linux 2.6.8 ignorarán el significado del parámetro de tamaño en epoll_create, es decir, el parámetro de tamaño ya no tiene importancia práctica y el sistema operativo ajustará el tamaño de la instancia de epoll según la situación.

epoll_ctl

        Después de crear una instancia de epoll, debe registrar el descriptor del archivo del objeto de monitoreo a través de la función epoll_ctl .

        El archivo de encabezado de referencia y la estructura de la función epoll_ctl son los siguientes:

#include <sys/epoll.h>

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

//成功时返回 0 ,失败时返回 -1

/* 参数含义 */
/* 
   epfd: 用于注册监视对象epoll实例的文件描述符
   op: 用于指定监视对象的添加、删除或修改等操作
   fd: 需要注册的监视对象文件描述符
   event: 监视对象的时间类型
*/

        El parámetro op tiene una macro diseñada para representar operaciones de adición, eliminación o modificación:

  • EPOLL_CTL_ADD: registra el descriptor de archivo en la instancia de epoll 
  • EPOLL_CTL_DEL: elimina el descriptor de archivo en la instancia de epoll 
  • EPOLL_CTL_MOD: cambia la ocurrencia de eventos de interés en los descriptores de archivos registrados

        Cuando se usa EPOLL_CTL_DEL , se debe pasar NULL al cuarto parámetro (evento).

Ejemplo:

//从实例 A 中删除文件描述符SOCK_1
epoll_ctl(A , EPOLL_CTL_DEL , SOCK_1, NULL);

//向实例 A 中注册文件描述符SOCK_1,并监视 EVENT中 EPOLLIN 事件的发生与否
struct epoll_event EVENT;
EVENT.events=EPOLLIN;
EVENT.data.fd=sockfd;
epoll_ctl(A , EPOLL_CTL_ADD , SOCK_1, &EVENT);

        En el ejemplo, EPOLLIN es un tipo de evento utilizado para representar " una situación en la que es necesario leer datos ". Existen otros tipos de eventos, como se muestra a continuación:

  • EPOLLIN: Cuando es necesario leer datos.
  • EPOLLOUT: el búfer de salida está vacío y los datos se pueden enviar inmediatamente.
  • EPOLLPRI: Situación cuando se reciben datos OOB.
  • EPOLLRDHUP: Situación desconectada o medio cerrada (comúnmente utilizada en modo de disparo por flanco).
  • EPOLLERR: Se produjo una condición de error.
  • EPOLLET: recibe notificaciones de eventos de forma activada por borde.
  • EPOLLONESHOT: después de que ocurre un evento, el descriptor de archivo correspondiente ya no recibe notificaciones de eventos. (Debe pasar EPOLL_CTL_MOD al segundo parámetro de la función epoll_ctL y configurar el evento nuevamente)

epoll_espera

        epoll_wait se utiliza para completar el monitoreo final del descriptor del archivo objeto .

        El archivo de encabezado de referencia y la estructura de la función epoll_wait son los siguientes:

#include <sys/epoll.h>

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

// 成功时返回发生事件的文件描述符数,失败时返回 -1。

/* 参数含义 */
/* 
   epfd: 表示事件发生监视范围的 epoll 实例 的文件描述符
   events: 保存发生事件的文件描述符集合的结构体地址值
   maxevents: 可以保存的最大事件数 (对应第二个参数events)
   timeout: 以 1/ 1000秒为单位的等待时间,传递 -1 时代表一直等待到事件的发生。
*/

        Cabe señalar que la variable de eventos (segundo parámetro) en la función epoll_wait necesita usar malloc para abrir espacio.

Por ejemplo:

struct epoll_event * EVENTS;

//EPOLL_SIZE是宏常量,其值代表欲开辟的实例数。注意最后要对类型进行强转,因为malloc返回的是void*
EVENTS = (struct epoll_event *) malloc(sizeof(struct epoll_event) * EPOLL_SIZE); 

epoll_wait(SOCK, EVENTS, EPOLL_SIZE, -1)

        A continuación, intentemos usar epoll para implementar el servidor de eco anterior.

Implementación del servidor Echo basado en epoll

Ejemplo de código:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 1024
#define EPOLL_SIZE 5

void Sender_message(char *message)
{
    puts(message);
    exit(1);
}

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;
    socklen_t adr_sz;
    int str_len, i;
    char buf[BUF_SIZE];

    struct epoll_event *ep_events;
    struct epoll_event event;
    int epfd, event_cnt;

    serv_sock = socket(PF_INET, SOCK_STREAM, 0);
    if (serv_sock == -1)
        Sender_message((char *)"sock creation error");

    memset(&serv_adr, 0, sizeof(serv_adr));

    serv_adr.sin_family = AF_INET;
    serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
    serv_adr.sin_port = htons(atoi(argv[1]));

    if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
        Sender_message((char *)"bind error");

    if (listen(serv_sock, 5) == -1)
        Sender_message((char *)"listen error");

    epfd = epoll_create(EPOLL_SIZE);
    ep_events = (struct epoll_event *)malloc(sizeof(struct epoll_event) * EPOLL_SIZE);

    event.events = EPOLLIN; //声明欲监视的事件为需要读取数据的情况
    event.data.fd = serv_sock; //将服务器端套接字保存至epoll_event结构体中
    epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

    while (1)
    {
        event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
        if (event_cnt == -1)
        {
            puts((char *)"supervise error");
            break;
        }

        for (i = 0; i < event_cnt; i++)
        {
            if (ep_events[i].data.fd == serv_sock) //若找到的文件描述符是服务器端的
            {
                adr_sz = sizeof(clnt_adr);
                clnt_sock =
                    accept(serv_sock, (struct sockaddr *)&clnt_adr, &adr_sz);
                event.events = EPOLLIN;
                event.data.fd = clnt_sock;
                epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
                printf((char *)"connected client: %d \n", clnt_sock);
            }
            else
            {
                str_len = read(ep_events[i].data.fd, buf, BUF_SIZE);
                if (str_len == 0) // 关闭请求
                {
                    epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
                    close(ep_events[i].data.fd);
                    printf((char *)"closed client: %d \n", ep_events[i].data.fd);
                }
                else
                {
                    write(ep_events[i].data.fd, buf, str_len); //写数据
                }
            }
        }
    }
    close(serv_sock);
    close(epfd);
    return 0;
}

resultado de la operación:

 Verifícate

Supongo que te gusta

Origin blog.csdn.net/weixin_42839065/article/details/131427564
Recomendado
Clasificación