modelo de reactor epoll (idea central de la biblioteca Libevent)

descripción general del modelo de reactor epoll 

El modelo del reactor epoll es el código central extraído de la biblioteca libevent.

Modo epoll ET + sin bloqueo, sondeo + anulación *ptr

Comprensión de los reactores: comprensión de referencia

        Después de agregar la transferencia IO, el servidor manejará el evento cuando ocurra. Lo mismo ocurre con el reactor aquí. Debido al complejo entorno de red, es posible que el servidor no pueda reescribir los datos directamente después de procesarlos , como cuando la red está ocupada o el búfer de la otra parte está lleno, en este caso no se puede volver a escribir directamente en el cliente. Después de procesar los datos, el reactor escucha el evento de escritura y solo entonces realiza la operación de reescritura cuando se puede volver a escribir en el cliente . Después de responder, cambie para escuchar los eventos de lectura. Etcétera.

 Comparar el modo normal de epoll

Las principales mejoras son las siguientes:

  • 1. Utilizando data.ptr (puntero genérico) en la estructura epoll_event , puede transportar su propia información y las funciones de devolución de llamada correspondientes al agregar el árbol rojo-negro. La función de devolución de llamada correspondiente se puede ejecutar cuando la función epoll_wait() devuelve el evento listo. 
The struct epoll_event is defined as:

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

           struct epoll_event {
               uint32_t     events;    /* Epoll events */
               epoll_data_t data;      /* User data variable */
           };
  • 2. Al responder a la solicitud de información del cliente, primero elimine el nodo correspondiente del árbol rojo-negro y modifique (data.ptr) su atributo a un evento de escritura (para garantizar que el cliente esté en un estado aceptable, como la ventana deslizante en la tecnología de comunicación TCP puede provocar un almacenamiento en caché insuficiente). Cuando la función epoll_wait() regrese la próxima vez, determine que el evento es un evento grabable, envíe datos al cliente, elimine el nodo del árbol rojo-negro, modifique (data.ptr) sus atributos a un evento legible y agregue nuevamente al árbol rojo-negro, esperando la próxima vez que regrese la función epoll_wait().

 Referencia detallada de los tres elementos del modelo del reactor epoll.

Revisión de estructura:

[Comprensión importante] Esta struct epoll_eventestructura puede entenderse como epoll——ctl()una estructura en el árbol rojo-negro que se puede montar (a través de) epoll en el kernel (similar a un mecanismo de copia profunda):

struct epoll_event {
	__uint32_t events; /* Epoll events */
	epoll_data_t data; /* User data variable */
};

typedef union epoll_data {
	void *ptr;
	int fd;				// 该fd就是传入epoll_ctl()的对应监听事件的fd
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;

Una unión también se denomina unión y las variables de la unión comparten un espacio de direcciones.

En el uso más básico, el valor puesto en la unión es fd , como se muestra en el siguiente pseudocódigo:

/* int connfd 是accpt()返回的socket连接句柄 */
struct epoll_event event = {0, {0}};
event.events = EPOLLIN;
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
    
......  // 业务逻辑
    
while (1) {
    /*监听红黑树efd, 将满足的事件的文件描述符加至events数组中, 阻塞wait*/
    int nfd = epoll_wait(efd, events, MAX_EVENTS+1, -1);

    for (i = 0; i < nfd; i++) {
		/* 使用int类型, 接收联合体data的fd成员 */
		int readyfd = events[i].data.fd;  
    
 		...... // 业务逻辑
     
	}
}

Pero el modelo del reactor no lo coloca directamente en fd, sino que coloca un puntero de estructura personalizada (convertido a la fuerza al tipo void *), de modo que cuando regresa epoll_wait (), se puede eliminar la estructura personalizada previamente almacenada.

/* 用户自定义结构体 */
/* 描述就绪文件描述符相关信息 */
struct myevent_s {
    int fd;                                                 //要监听的文件描述符
    int events;                                             //对应的监听事件
    void *arg;                                              //泛型参数
    void (*call_back)(int fd, int events, void *arg);       //回调函数

    int status;                                             //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
    char buf[BUFLEN];
    int len;
    long last_active;                                       //记录每次加入红黑树 g_efd 的时间值
};

...... // 业务逻辑

/* struct myevent_s *ev 是用户自定义结构体 */
struct epoll_event epv = {0, {0}};
epv.events = ev->events = EPOLLIN;   		//EPOLLIN 或 EPOLLOUT
epv.data.ptr = ev;							// 注意这里不是epv.data.fd = connfd
epoll_ctl(efd, EPOLL_CTL_ADD, ev->fd, &epv)
    
...... // 业务逻辑
    
while(1) {
    
/*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/
	int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);

	...... //出错处理
    
	for (i = 0; i < nfd; i++) {
		/*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/
		struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;  
    
 		...... // 业务逻辑
     
	}
}

Implementación del mecanismo de devolución de llamada:

        Almacene la función de puntero en la estructura personalizada. Después de que epoll_wait regrese, saque events[i].data.ptrla estructura personalizada señalada y luego llame a la función de devolución de llamada almacenada en la estructura :

struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
...... //判断
ev->call_back(ev->fd, events[i].events, ev->arg);

La lógica general del modelo del reactor epoll.

  • socket, bind, listening – epoll_create crea un árbol rojo-negro de escucha – devuelve dfpe
  • epoll_ctl() agrega un listeningfd (toma de escucha) al árbol rojo-negro 
  • mientras(1) {

        1. [Opcional] Supervise 100 conexiones en cada ronda de iteraciones. Si hay una conexión de tiempo de espera (usuario depositado), se cerrará activamente;

       2. epoll_wait() escucha --> ocurre un evento correspondiente al fd de escucha --> devuelve el conjunto de estructuras de satisfacción de escucha (es decir, la matriz de estructura struct epoll_event);

        3. Determine los elementos de la matriz devueltos:

 } // while(1) end

flujo de código 

Código original:

socket, bind, listening
efd = epoll_create crea un oyente (árbol rojo-negro)
epoll_ctl agrega un oyente fd al árbol
for(;;) {     matriz satisfecha = epoll_wait escucha (escuchar fd tiene un evento y devuelve una matriz satisfecha)     si el el elemento de la matriz es lfd {          aceptar     }     el elemento de la matriz es cfd {         leer pequeño ->         escribir         grande     } }









reactor:

socket, bind, listening
efd = epoll_create crea un oyente (árbol rojo-negro)
epoll_ctl agrega un oyente fd al árbol
for(;;) {     matriz satisfecha = epoll_wait escucha (escuchar fd tiene un evento y devuelve una matriz satisfecha)     si el El elemento de la matriz es lfd {          aceptar     }     El elemento de la matriz es cfd {         leer         pequeño ->         cfd grande Retire el EPOLLIN de cfd del árbol rojo-negro         y cámbielo a EPOLLOUT         cfd. Agregue y escriba la función de devolución de llamada         EPOLL_CTL_ADD y vuelva a colocarla en el árbol rojo-negro para escuchar eventos de escritura         (luego espere a que regrese epoll_wait,           explique que cfd se puede escribir, y luego escriba,           cfd saca EPOLLOUT del árbol rojo-negro y lo cambia a EPOLLIN,           y luego lo vuelve a colocar en el árbol rojo-negro para escuchar eventos de lectura)     } }
















El reactor determina principalmente si el par tiene capacidad de escritura . El reactor no solo monitorea los eventos de lectura de CFD , sino que también monitorea los eventos de escritura de CFD. 

Código de caso 

/*
 *epoll基于非阻塞I/O事件驱动
 */
#include <stdio.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>

#define MAX_EVENTS  1024                                    //监听上限数
#define BUFLEN 4096                                         // 缓冲区大小
#define SERV_PORT   8080                                    // 端口号

void recvdata(int fd, int events, void *arg);
void senddata(int fd, int events, void *arg);

/* 描述就绪文件描述符相关信息 */

struct myevent_s {
    int fd;                                                 //要监听的文件描述符
    int events;                                             //对应的监听事件
    void *arg;                                              //泛型参数
    void (*call_back)(int fd, int events, void *arg);       //回调函数
    int status;                                             //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
    char buf[BUFLEN];                                       //缓冲区
    int len;                                                //缓冲区大小
    long last_active;                                       //记录每次加入红黑树 g_efd 的时间值
};

int g_efd;                                                  //全局变量, 保存epoll_create返回的文件描述符
struct myevent_s g_events[MAX_EVENTS+1];                    //自定义结构体类型数组. +1-->listen fd


/*将结构体 myevent_s 成员变量 初始化*/

void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg)
{
    ev->fd = fd;
    ev->call_back = call_back;
    ev->events = 0;
    ev->arg = arg;
    ev->status = 0;
    memset(ev->buf, 0, sizeof(ev->buf));
    ev->len = 0;
    ev->last_active = time(NULL);                       //调用eventset函数的时间

    return;
}

/* 向 epoll监听的红黑树 添加一个 文件描述符 */

//eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);
void eventadd(int efd, int events, struct myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};
    int op;
    epv.data.ptr = ev;
    epv.events = ev->events = events;       //EPOLLIN 或 EPOLLOUT

    if (ev->status == 0) {                                          //已经在红黑树 g_efd 里
        op = EPOLL_CTL_ADD;                 //将其加入红黑树 g_efd, 并将status置1
        ev->status = 1;
    }

    if (epoll_ctl(efd, op, ev->fd, &epv) < 0)                       //实际添加/修改
        printf("event add failed [fd=%d], events[%d]\n", ev->fd, events);
    else
        printf("event add OK [fd=%d], op=%d, events[%0X]\n", ev->fd, op, events);

    return ;
}

/* 从epoll 监听的 红黑树中删除一个 文件描述符*/

void eventdel(int efd, struct myevent_s *ev)
{
    struct epoll_event epv = {0, {0}};

    if (ev->status != 1)                                        //不在红黑树上
        return ;

    //epv.data.ptr = ev;
    epv.data.ptr = NULL;
    ev->status = 0;                                             //修改状态
    epoll_ctl(efd, EPOLL_CTL_DEL, ev->fd, &epv);                //从红黑树 efd 上将 ev->fd 摘除

    return ;
}

/*  当有文件描述符就绪, epoll返回, 调用该函数 与客户端建立链接 */

void acceptconn(int lfd, int events, void *arg)
{
    struct sockaddr_in cin;                                            // client addr
    socklen_t len = sizeof(cin);
    int cfd, i;

    if ((cfd = accept(lfd, (struct sockaddr *)&cin, &len)) == -1) {
        if (errno != EAGAIN && errno != EINTR) {
            /* 暂时不做出错处理 */
        }
        printf("%s: accept, %s\n", __func__, strerror(errno));         //__func__表当前函数的名字
        return ;
    }

    do {
        for (i = 0; i < MAX_EVENTS; i++)                                //从全局数组g_events中找一个空闲元素
            if (g_events[i].status == 0)                                //类似于select中找值为-1的元素
                break;                                                  //跳出 for

        if (i == MAX_EVENTS) {
            printf("%s: max connect limit[%d]\n", __func__, MAX_EVENTS);
            break;                                                      //跳出do while(0) 不执行后续代码
        }

        int flag = 0;
        if ((flag = fcntl(cfd, F_SETFL, O_NONBLOCK)) < 0) {             //将cfd也设置为非阻塞
            printf("%s: fcntl nonblocking failed, %s\n", __func__, strerror(errno));
            break;
        }

        /* 给cfd设置一个 myevent_s 结构体, 回调函数 设置为 recvdata */
        eventset(&g_events[i], cfd, recvdata, &g_events[i]);   
        eventadd(g_efd, EPOLLIN, &g_events[i]);                         //将cfd添加到红黑树g_efd中,监听读事件

    } while(0);

    printf("new connect [%s:%d][time:%ld], pos[%d]\n", 
            inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), g_events[i].last_active, i);
    return ;
}

void recvdata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;

    len = recv(fd, ev->buf, sizeof(ev->buf), 0);            //读文件描述符, 数据存入myevent_s成员buf中

    eventdel(g_efd, ev);        //将该节点从红黑树上摘除

    if (len > 0) {

        ev->len = len;
        ev->buf[len] = '\0';                                //手动添加字符串结束标记
        printf("C[%d]:%s\n", fd, ev->buf);

        eventset(ev, fd, senddata, ev);                     //设置该 fd 对应的回调函数为 senddata
        eventadd(g_efd, EPOLLOUT, ev);                      //将fd加入红黑树g_efd中,监听其写事件

    } else if (len == 0) {
        close(ev->fd);
        /* ev-g_events 地址相减得到偏移元素位置 */
        printf("[fd=%d] pos[%ld], closed\n", fd, ev-g_events);
    } else {
        close(ev->fd);
        printf("recv[fd=%d] error[%d]:%s\n", fd, errno, strerror(errno));
    }

    return;
}

void senddata(int fd, int events, void *arg)
{
    struct myevent_s *ev = (struct myevent_s *)arg;
    int len;

    len = send(fd, ev->buf, ev->len, 0);                    //直接将数据 回写给客户端。未作处理

    eventdel(g_efd, ev);                                //从红黑树g_efd中移除

    if (len > 0) {

        printf("send[fd=%d], [%d]%s\n", fd, len, ev->buf);
        eventset(ev, fd, recvdata, ev);                     //将该fd的 回调函数改为 recvdata
        eventadd(g_efd, EPOLLIN, ev);                       //从新添加到红黑树上, 设为监听读事件

    } else {
        close(ev->fd);                                      //关闭链接
        printf("send[fd=%d] error %s\n", fd, strerror(errno));
    }

    return ;
}

/*创建 socket, 初始化lfd */

void initlistensocket(int efd, short port)
{
    struct sockaddr_in sin;

    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(lfd, F_SETFL, O_NONBLOCK);                                            //将socket设为非阻塞

	memset(&sin, 0, sizeof(sin));                                               //bzero(&sin, sizeof(sin))
	sin.sin_family = AF_INET;
	sin.sin_addr.s_addr = INADDR_ANY;
	sin.sin_port = htons(port);

	bind(lfd, (struct sockaddr *)&sin, sizeof(sin));

	listen(lfd, 20);

    /* void eventset(struct myevent_s *ev, int fd, void (*call_back)(int, int, void *), void *arg);  */
    eventset(&g_events[MAX_EVENTS], lfd, acceptconn, &g_events[MAX_EVENTS]);

    /* void eventadd(int efd, int events, struct myevent_s *ev) */
    eventadd(efd, EPOLLIN, &g_events[MAX_EVENTS]);

    return ;
}

int main(int argc, char *argv[])
{
    unsigned short port = SERV_PORT;

    if (argc == 2)
        port = atoi(argv[1]);                           //使用用户指定端口.如未指定,用默认端口

    g_efd = epoll_create(MAX_EVENTS+1);                 //创建红黑树,返回给全局 g_efd 
    if (g_efd <= 0)
        printf("create efd in %s err %s\n", __func__, strerror(errno));

    initlistensocket(g_efd, port);                      //初始化监听socket

    struct epoll_event events[MAX_EVENTS+1];            //保存已经满足就绪事件的文件描述符数组 
	printf("server running:port[%d]\n", port);

    int checkpos = 0, i;
    while (1) {
        /* 超时验证,每次测试100个链接,不测试listenfd 当客户端60秒内没有和服务器通信,则关闭此客户端链接 */

        long now = time(NULL);                          //当前时间
        for (i = 0; i < 100; i++, checkpos++) {         //一次循环检测100个。 使用checkpos控制检测对象
            if (checkpos == MAX_EVENTS)
                checkpos = 0;
            if (g_events[checkpos].status != 1)         //不在红黑树 g_efd 上
                continue;

            long duration = now - g_events[checkpos].last_active;       //客户端不活跃的世间

            if (duration >= 60) {
                close(g_events[checkpos].fd);                           //关闭与该客户端链接
                printf("[fd=%d] timeout\n", g_events[checkpos].fd);
                eventdel(g_efd, &g_events[checkpos]);                   //将该客户端 从红黑树 g_efd移除
            }
        }

        /*监听红黑树g_efd, 将满足的事件的文件描述符加至events数组中, 1秒没有事件满足, 返回 0*/
        int nfd = epoll_wait(g_efd, events, MAX_EVENTS+1, 1000);
        if (nfd < 0) {
            printf("epoll_wait error, exit\n");
            break;
        }

        for (i = 0; i < nfd; i++) {
            /*使用自定义结构体myevent_s类型指针, 接收 联合体data的void *ptr成员*/
            struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;  

            if ((events[i].events & EPOLLIN) && (ev->events & EPOLLIN)) {           //读就绪事件
                ev->call_back(ev->fd, events[i].events, ev->arg);
                //lfd  EPOLLIN  
            }
            if ((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT)) {         //写就绪事件
                ev->call_back(ev->fd, events[i].events, ev->arg);                    
            }
        }
    }

    /* 退出前释放所有资源 */
    return 0;
}

Supongo que te gusta

Origin blog.csdn.net/weixin_43200943/article/details/130149510
Recomendado
Clasificación