reactor实现

 

阅读之前需要先了解一下5中I/O模型的比较。

Reactor模式

Reactor(反应堆模式)是libevent的中心思想,常规的I/O多路复用中采用select和poll、epoll等来实现。而Reactor是将上述机制进一步封装,通俗的来讲,就是通过回调机制实现。我们只需将事件的接口注册到Reactor上,当事件发生之后,会回调注册的接口。

Reactor是一种事件驱动机制。和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完成处理,而是恰恰相反,Reactor逆置了事件处理流程,应用程序需要提供相应的接口并注册到Reactor上,如果相应的事件发生,Reactor将主动调用应用程序注册的接口,这些接口又称为“回调函数”。

Reactor模式结构

在Reactor模式中,有以下几个关键的参与者。
描述符(handle)由操作系统提供,用于识别每一个事件,如Socket描述符、文件描述符等。在Linux中,它用一个整数来表示。事件可以来自外部,如来自客户端的连接请求、数据等。事件也可以来自内部,如定时器事件。

同步事件分离器(demultiplexer)是一个函数,用来等待一个或多个事件的发生。调用者会被阻塞,直到分离器分离的描述符集上有事件发生。Linux的select函数是一个经常被使用的分离器。
事件处理器接口(event handler)是由一个或多个模板函数组成的接口。这些模板函数描述了和应用程序相关的对某个事件的操作。 具体的事件处理器:是事件处理器接口的实现。它实现了应用程序提供的某个服务。每个具体的事件处理器总和一个描述符相关。它使用描述符来识别事件、识别应用程序提供的服务。
Reactor 管理器(reactor):定义了一些接口,用于应用程序控制事件调度,以及应用程序注册、删除事件处理器和相关的描述符。它是事件处理器的调度核心。 Reactor管理器使用同步事件分离器来等待事件的发生。一旦事件发生,Reactor管理器先是分离每个事件,然后调度事件处理器,最后调用相关的模 板函数来处理这个事件。
通过上述分析,我们注意到,是Reactor管理器而不是应用程序负责等待事件、分离事件和调度事件。实际上,Reactor管理器并没有被具体的 事件处理器调用,而是管理器调度具体的事件处理器,由事件处理器对发生的事件做出处理。这就是类似Hollywood原则的“反向控制”。应用程序要做的 仅仅是实现一个具体的事件处理器,然后把它注册到Reactor管理器中。接下来的工作由管理器来完成。这些参与者的相互关系如下图所示。

 

 

Reactor 处理步骤

  1. 初始化一个Reactor管理器
  2. 初始化事件处理器,设置事件源及回调函数
  3. 将事件处理器注册到Reactor管理器上
  4. 注册该事件
  5. 进入循环等待事件发生并处理

优点

  1. 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;
  2. 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
  3. 可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;
  4. 可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

 

复制代码
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <sys/poll.h>
#include <sys/epoll.h>


#include <pthread.h>
 
#define MAXLNE  4096

#define POLL_SIZE    1024

#define BUFFER_LENGTH        1024
#define MAX_EPOLL_EVENT        1024

#define NOSET_CB    0
#define READ_CB        1
#define WRITE_CB    2
#define ACCEPT_CB    3


typedef int NCALLBACK(int fd, int event, void *arg);


struct nitem { // fd

    int fd;

    int status;
    int events;
    void *arg;
#if 0
    NCALLBACK callback;
#else
    NCALLBACK *readcb;   // epollin
    NCALLBACK *writecb;  // epollout
    NCALLBACK *acceptcb; // epollin
#endif
    unsigned char sbuffer[BUFFER_LENGTH]; //
    int slength;

    unsigned char rbuffer[BUFFER_LENGTH];
    int rlength;
    
};

struct itemblock {

    struct itemblock *next;
    struct nitem *items;

};

struct reactor {

    int epfd;
    struct itemblock *head; 

};

int init_reactor(struct reactor *r);

int read_callback(int fd, int event, void *arg);

int write_callback(int fd, int event, void *arg);

int accept_callback(int fd, int event, void *arg);


struct reactor *instance = NULL;

struct reactor *getInstance(void) { //singleton

    if (instance == NULL) {

        instance = malloc(sizeof(struct reactor));
        if (instance == NULL) return NULL;
        memset(instance, 0, sizeof(struct reactor));

        if (0 > init_reactor(instance)) {
            free(instance);
            return NULL;
        }

    }

    return instance;
}



int nreactor_set_event(int fd, NCALLBACK cb, int event, void *arg) {

    struct reactor *r = getInstance();
    
    struct epoll_event ev = {0};
    
    if (event == READ_CB) {
        r->head->items[fd].fd = fd;
        r->head->items[fd].readcb = cb;
        r->head->items[fd].arg = arg;

        ev.events = EPOLLIN;
        
    } else if (event == WRITE_CB) {
        r->head->items[fd].fd = fd;
        r->head->items[fd].writecb = cb;
        r->head->items[fd].arg = arg;

        ev.events = EPOLLOUT;
    } else if (event == ACCEPT_CB) {
        r->head->items[fd].fd = fd;
        r->head->items[fd].acceptcb = cb;
        r->head->items[fd].arg = arg;

        ev.events = EPOLLIN;
    }

    ev.data.ptr = &r->head->items[fd];

    
    if (r->head->items[fd].events == NOSET_CB) {
        if (epoll_ctl(r->epfd, EPOLL_CTL_ADD, fd, &ev) < 0) {
            printf("epoll_ctl EPOLL_CTL_ADD failed, %d\n", errno);
            return -1;
        }
        r->head->items[fd].events = event;
    } else if (r->head->items[fd].events != event) {

        if (epoll_ctl(r->epfd, EPOLL_CTL_MOD, fd, &ev) < 0) {
            printf("epoll_ctl EPOLL_CTL_MOD failed\n");
            return -1;
        }
        r->head->items[fd].events = event;
    }

    
    return 0;
}

int nreactor_del_event(int fd, NCALLBACK cb, int event, void *arg) {

    struct reactor *r = getInstance();
    
    struct epoll_event ev = {0};
    ev.data.ptr = arg;

    epoll_ctl(r->epfd, EPOLL_CTL_DEL, fd, &ev);
    r->head->items[fd].events = 0;

    return 0;
}



int write_callback(int fd, int event, void *arg) {

    struct reactor *R = getInstance();
    
    unsigned char *sbuffer = R->head->items[fd].sbuffer;
    int length = R->head->items[fd].slength;

    int ret = send(fd, sbuffer, length, 0);

    if (ret < length) {
        nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
    } else {
        nreactor_set_event(fd, read_callback, READ_CB, NULL);
    }
    return 0;
}

// 5k qps

int read_callback(int fd, int event, void *arg) {

    struct reactor *R = getInstance();

    unsigned char *buffer = R->head->items[fd].rbuffer;

    
#if 0 //ET
    int idx = 0, ret = 0;
    while (idx < BUFFER_LENGTH) {

        ret = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
        if (ret == -1) { 
            break;
        } else if (ret > 0) {
            idx += ret;
        } else {// == 0
            break;
        }

    }

    if (idx == BUFFER_LENGTH && ret != -1) {
        nreactor_set_event(fd, read_callback, READ_CB, NULL);
    }
    else if (ret == 0) {
        nreactor_del_event(fd, NULL, 0, NULL);   
        close(fd);
    } 
    else {
        nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
    }
    
#else //LT

    int ret = recv(fd, buffer, BUFFER_LENGTH, 0);
    if (ret == 0) { // fin
        
        nreactor_del_event(fd, NULL, 0, NULL);
        close(fd);
        
    } else if (ret > 0) {

        unsigned char *sbuffer = R->head->items[fd].sbuffer;
        memcpy(sbuffer, buffer, ret);
        R->head->items[fd].slength = ret;

        printf("readcb: %s\n", sbuffer);
        nreactor_set_event(fd, write_callback, WRITE_CB, NULL);  //还要等待下一个事件写吗?
    }
        
#endif

    

}


// web server 
// ET / LT
int accept_callback(int fd, int event, void *arg) {

    int connfd;
    struct sockaddr_in client;
    socklen_t len = sizeof(client);
    if ((connfd = accept(fd, (struct sockaddr *)&client, &len)) == -1) {
        printf("accept socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }

    nreactor_set_event(connfd, read_callback, READ_CB, NULL);

}



int init_server(int port) {

    int listenfd;
    struct sockaddr_in servaddr;
    char buff[MAXLNE];
 
    if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
 
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(port);
 
    if (bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1) {
        printf("bind socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
 
    if (listen(listenfd, 10) == -1) {
        printf("listen socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }
    return listenfd;

}

int init_reactor(struct reactor *r) {

    if (r == NULL) return -1;

    int epfd = epoll_create(1); //int size
    r->epfd = epfd;

    // fd --> item
    r->head = (struct itemblock*)malloc(sizeof(struct itemblock));
    if (r->head == NULL) {
        close(epfd);
        return -2;
    } 
    memset(r->head, 0, sizeof(struct itemblock));

    r->head->items = malloc(MAX_EPOLL_EVENT * sizeof(struct nitem));
    if (r->head->items == NULL) {
        free(r->head);
        close(epfd);
        return -2;
    }
    memset(r->head->items, 0, (MAX_EPOLL_EVENT * sizeof(struct nitem)));
    
    r->head->next = NULL;
    
    return 0;
}

// accept --> EPOLL
int reactor_loop(int listenfd) {

    struct reactor *R = getInstance();
    
    struct epoll_event events[POLL_SIZE] = {0};
    while (1) {

        int nready = epoll_wait(R->epfd, events, POLL_SIZE, 5);
        if (nready == -1) {
            continue;
        }

        int i = 0;
        for (i = 0;i < nready;i ++) {
            
            struct nitem *item = (struct nitem *)events[i].data.ptr;
            int connfd = item->fd;

            if (connfd == listenfd) { //
                item->acceptcb(listenfd, 0, NULL);
            } else {
            
                if (events[i].events & EPOLLIN) { //
                    item->readcb(connfd, 0, NULL);
                
                } 
                if (events[i].events & EPOLLOUT) {
                    item->writecb(connfd, 0, NULL);
        
                }
            }
        }

    }

    return 0;
}


int main(int argc, char **argv) 
{
    
     int  connfd, n;

    int listenfd = init_server(9999);
    nreactor_set_event(listenfd, accept_callback, ACCEPT_CB, NULL);

    //nreactor_set_event(listenfd, accept_callback, read_callback, write_callback);
    

    reactor_loop(listenfd);
     
    return 0;
}
复制代码

 

 

总结:

reactor:每一个fd对应一个socket_item, 事件驱动,当fd是一个读事件时,回调可读的函数,读到reader_buffer,写事件写到write_buffer.

reactor在epoll的基础上,有了那些好处?

1.epoll对io管理,reactor对事件的管理。

2.由于socket_item封装,对未处理的事件,放到一个独立的buffer里面。

 

为什么不能写全局的函数,而是回调?

因为epoll监听多种io,磁盘io,网络io等等。封装各自的回调函数

————————————————
版权声明:本文为CSDN博主「菜鸡渣渣刘」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_35551407/article/details/107141036

posted @   放弃吧  阅读(54)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示