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 <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/epoll.h>


#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 <sys/socket.h>
#include <sys/epoll.h>

我们先通过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 模式优势

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

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

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

<think>好的,我现在需要帮助用户了解Redis中Reactor模式的工作原理和实现方式。首先,我得回顾一下Reactor模式的基本概念,然后结合Redis的具体实现来分析。 用户提供的引用中有提到Reactor模型,特别是单Reactor单线程的模型,这可能和Redis的单线程模型有关。根据引用[1],Reactor模型分为单线程、多线程和主从多Reactor几种。而Redis主要使用的是单Reactor单线程模型,这在处理网络I/O和命令执行时非常关键。 接下来,我需要确认Redis的Reactor模式如何工作。根据引用[2],Reactor模型通过事件驱动处理I/O,将事件分发给对应的处理器。Redis作为单线程应用,它的事件循环应该包括监听套接字、处理连接、读取请求、执行命令和返回结果这些步骤。 用户提供的引用[3]提到Reactor模式解耦并发请求并分发给事件处理器,这对应到Redis中可能就是文件事件处理器处理不同的事件类型,如连接事件、读事件、写事件等。此外,Redis使用I/O多路复用(如epoll或kqueue)来高效处理大量连接,这也是Reactor模式的关键部分。 另外,引用[4]提到Redis的字符串编码方式,这部分可能和当前问题无关,但需要确保不混淆信息。需要专注在Reactor模式的实现上。 现在需要整理Redis Reactor的具体步骤:启动时绑定端口,初始化事件循环,注册连接事件处理器。当客户端连接时,accept处理并注册读事件。当有数据可读时,读取解析命令,执行后结果写入缓冲区,注册写事件待发送。这整个过程都是在单线程中完成的,避免了多线程的竞争问题,但可能受限于单核性能。 另外,要注意Redis虽然是单线程处理命令,但某些操作如持久化会使用子进程,但主流程还是单线程Reactor。需要明确区分这些点,避免用户误解。 可能用户还会关心为什么Redis选择单线程Reactor,这可能涉及到简单性、避免锁开销、减少上下文切换等优点。但需要引用中的信息支持这一点,或者结合已知知识补充。 总结回答结构应该是:先介绍Reactor模式的基本概念,然后说明Redis中的实现方式,包括事件循环、I/O多路复用、事件分发和处理,最后分析其优缺点。同时生成相关问题时要围绕Redis的Reactor实现、优缺点、对比其他模型等。</think>### Redis中Reactor模式的实现与工作原理 #### 1. Reactor模式的核心思想 Reactor模式是一种**事件驱动**的I/O处理模型,核心是通过**事件分发机制**解耦I/O操作与业务逻辑。其核心组件包括: - **事件源**(如套接字) - **多路复用器**(I/O多路复用模块,如`epoll`) - **事件处理器**(处理具体事件的回调函数) 在Redis中,这一模式被用于高效处理大量客户端连接[^2][^3]。 #### 2. Redis的单线程Reactor实现 Redis采用**单Reactor单线程**模型,具体流程如下: 1. **初始化阶段** ```c // 伪代码示例 aeEventLoop *eventLoop = aeCreateEventLoop(); // 创建事件循环 listenSocket = socket_bind(6379); // 绑定端口 aeCreateFileEvent(eventLoop, listenSocket, AE_READABLE, acceptHandler); // 注册连接事件 ``` 此时主线程仅监听新连接请求。 2. **事件循环核心流程** $$aeProcessEvents(eventLoop) = \text{epoll\_wait() + 回调触发}$$ 通过`epoll/kqueue`等待I/O事件,当事件触发时调用预注册的处理器: - **连接事件** → `acceptHandler()`:接受连接并注册**读事件** - **读事件** → `readQueryFromClient()`:读取命令并解析 - **写事件** → `sendReplyToClient()`:将响应数据返回客户端 3. **命令执行阶段** 所有Redis命令(如`SET`, `GET`)均在**主线程顺序执行**,避免了多线程竞争问题[^1]。 #### 3. 关键设计优势 | 特性 | 说明 | |--------------|----------------------------------------------------------------------| | **无锁设计** | 单线程执行命令,无需同步锁 | | **低延迟** | 通过I/O多路复用实现高吞吐量(如单实例支持10万+ QPS) | | **简单性** | 规避多线程调试复杂度 | #### 4. 性能边界与优化 - **瓶颈场景**:超长耗时命令(如`KEYS *`)会阻塞事件循环 - **解决方案**: - 使用`lazy-free`机制异步释放内存 - 通过`Redis Module`支持多线程扩展特定操作 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值