Reactor 模式

目录

Reactor 模式核心组件(C 语言视角)

定义conn_item结构体

设置事件监听函数set_event()

定义三个事件对应的回调函数1.accept_cb()  2.recv_cb() 3.send_cb

结果展示

Reactor 模式优势


Reactor 模式是一种事件驱动的设计模式,核心是通过一个中心组件(Reactor)监听多种事件(如网络 I/O 事件、定时器事件等),当事件发生时,将事件分发给对应的处理器处理。在 C 语言中,常基于操作系统的 I/O 多路复用机制(如 epollselectkqueue)实现,用于高效处理高并发场景。

Reactor 模式核心组件(C 语言视角)

事件循环(Event Loop):循环监听事件,调用操作系统的 I/O 多路复用接(如 epoll_wait),阻塞等待事件就绪。

事件处理器(Event Handler):定义事件处理的回调函数,如读事件处理函数、写事件处理函数。

事件注册模块:将需要监听的事件(如文件描述符的读 / 写事件)注册到 Reactor 中,关联对应的处理器。

#if 1
#include 
#include 
#include 
#include 
#include 
#include 


#define  BUFFER_LENGTH 1024
typedef int(*RCALLBACK)(int fd);
struct conn_item{
    int fd;


    char rbuffer[BUFFER_LENGTH];
    int rlen;
    char wbuffer[BUFFER_LENGTH];
    int wlen;


    union {
        RCALLBACK accept_callback;
        RCALLBACK recv_callback;
    }recv_t;
        RCALLBACK send_callback;
};


int epfd=0;

struct conn_item connList[1024]={0};

#if  0
struct reactor{
    int epfd;
    struct conn_item*connList;
};
#else

#endif

int set_event(int fd,int event,int flag){
    if(flag){ //1.add  0.mod
        struct epoll_event ev;
        ev.events = event ;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }else{
        struct epoll_event ev;
        ev.events = event ;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}

int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd );

// 回调函数
//listenFd
int accept_cb(int fd){
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);

    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
    if (clientfd<0) return -1;

    set_event(clientfd,EPOLLIN,1);

    connList[clientfd].fd=clientfd;
    bzero(connList[clientfd].rbuffer,sizeof (connList[clientfd].rbuffer));
    connList[clientfd].rlen=0;

    connList[clientfd].recv_t.recv_callback= recv_cb;
    connList[clientfd].send_callback= send_cb;
    return clientfd;
}

//clientFd
int recv_cb(int fd){
    char *buffer = connList[fd].rbuffer;
    int idx =connList[fd].rlen;

    int count = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
    if (count == 0) {
        printf("disconnect\n");

        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        close(fd);
        return -1;
    }
    set_event(fd,EPOLLOUT,0);

    connList[fd].rlen+=count;

#if 1
    memcpy(connList[fd].wbuffer,connList[fd].rbuffer,connList[fd].rlen);
    connList[fd].wlen=connList[fd].rlen;

#endif

    return count;
}
int send_cb(int fd ){
    char *buffer = connList[fd].wbuffer;
    int idx =connList[fd].wlen;
    int count =send(fd, buffer, idx, 0);
    struct epoll_event ev;
    ev.events = EPOLLIN ;
    ev.data.fd = fd;
    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);

    set_event(fd,EPOLLIN,0);

    return count;
}
// tcp
int main() {

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(struct sockaddr_in));

    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
    serveraddr.sin_port = htons(2048);

    if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {
        perror("bind");
        return -1;
    }
    int reuse =1; //允许重用
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    if (ret==-1) perror("setsockopt");

    listen(sockfd, 10);

    connList[sockfd].fd=sockfd;
    connList[sockfd].recv_t.accept_callback = accept_cb;


    epfd = epoll_create(1); // int size
    //pthread_create();
    set_event(sockfd,EPOLLIN,1);

    struct epoll_event revents[1024] = {0};
    while (1) {
        int nready = epoll_wait(epfd, revents, 1024, -1);

        int i = 0;
        for (i = 0;i < nready;i++) {
            int connfd = revents[i].data.fd;
        if (revents[i].events & EPOLLIN) {
                int count =connList[connfd].recv_t.recv_callback(connfd);
                printf("recv count%d <-- buffer: %s\n",count,connList[connfd].rbuffer);

            }else if (revents[i].events & EPOLLOUT){
                int count =connList[connfd].send_callback(connfd);
                printf("send --> buffer: %s\n",  connList[connfd].wbuffer);
            }
        }
    }
    getchar();
    //close(clientfd);
}

#endif




这是基于epoll实现的一个reactor模式

实现reactor模式的时候我们要包含相关系统调用的头文件

#include 
#include 

我们先通过TCP三次握手 绑定一个自动分配的IP地址 和 2048端口号的服务器

rector模式的本质是把响应IO 转变 为 响应事件 通过对事件的响应调用不同的回调函数实现不同的功能

定义conn_item结构体
#define  BUFFER_LENGTH 1024
typedef int(*RCALLBACK)(int fd);
struct conn_item{
    int fd;


    char rbuffer[BUFFER_LENGTH];
    int rlen;
    char wbuffer[BUFFER_LENGTH];
    int wlen;
    
    union {
        RCALLBACK accept_callback;
        RCALLBACK recv_callback;
    }recv_t;
        RCALLBACK send_callback;
};

定义一个 conn_item结构体  该结构体包含的成员分别有

int fd :对应的文件描述符

char rbuffer :接收客户端发送的数据并保存

char wbuffer:发送给客户端的数据并保存

三个函数指针分别为

指向 accept_cb的函数指针accept_callback

指向recv_cb的函数指针recv_callback

指向send的函数指针send_callback

其中前两个函数指针是进行了union联合响应

设置事件监听函数set_event()
int set_event(int fd,int event,int flag){
    if(flag){ //1.add  0.mod
        struct epoll_event ev;
        ev.events = event ;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
    }else{
        struct epoll_event ev;
        ev.events = event ;
        ev.data.fd = fd;
        epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
    }
}
我们通过传入fd文件描述符 和 我们需要传入的事件状态event 如EPOLLIN  EPOLLOUT 读和写操作

定义一个flag用于我们区分是否要添加还是转移

定义三个事件对应的回调函数1.accept_cb()  2.recv_cb() 3.send_cb
// 回调函数
//listenFd
int accept_cb(int fd){
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);

    int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);
    if (clientfd<0) return -1;

    set_event(clientfd,EPOLLIN,1);

    connList[clientfd].fd=clientfd;
    bzero(connList[clientfd].rbuffer,sizeof (connList[clientfd].rbuffer));
    connList[clientfd].rlen=0;

    connList[clientfd].recv_t.recv_callback= recv_cb;
    connList[clientfd].send_callback= send_cb;
    return clientfd;
}

//clientFd
int recv_cb(int fd){
    char *buffer = connList[fd].rbuffer;
    int idx =connList[fd].rlen;

    int count = recv(fd, buffer+idx, BUFFER_LENGTH-idx, 0);
    if (count == 0) {
        printf("disconnect\n");

        epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
        close(fd);
        return -1;
    }
    set_event(fd,EPOLLOUT,0);

    connList[fd].rlen+=count;

#if 1
    memcpy(connList[fd].wbuffer,connList[fd].rbuffer,connList[fd].rlen);
    connList[fd].wlen=connList[fd].rlen;

#endif

    return count;
}
int send_cb(int fd ){
    char *buffer = connList[fd].wbuffer;
    int idx =connList[fd].wlen;
    int count =send(fd, buffer, idx, 0);
    struct epoll_event ev;
    set_event(fd,EPOLLIN,0);

    return count;
}

当有客户端连接的时候事件响应自动调用accept_cb函数

通过accept和客户端建立连接 并获取和客户端进行通信的文件描述符clientfd

我们通过set_event接口把clientfd加入到监听集合中

我们通过函数指针把clientfd传入到recv_cb回调函数中

我们通过rbuffer字符数组 读取客户端发送到本服务器的数据

读完数据之后我们通过set_event接口把该文件描述符的监听状态转移成写操作

因为写就绪是一直触发的 当我们第一次触发写就绪事件的时候我们进入到了send_cb函数中

写完之后立马把通过set_event把写状态改成读状态

结果展示

Reactor 模式_第1张图片

Reactor 模式优势

高效处理并发:基于 I/O 多路复用,单线程可管理大量文件描述符。

解耦事件与处理:事件监听和处理逻辑分离,代码结构清晰,扩展性强。

资源利用率高:仅在事件就绪时执行处理,减少无效等待,适合网络服务器、高性能 I/O 应用等场景

你可能感兴趣的:(服务器,c语言,linux,tcp/ip,网络)