Reactor原理与实现

Reactor模式是一种处理并发IO的高效策略,它通过集中管理IO事件,使用回调函数处理不同类型的IO操作。文章详细介绍了Reactor如何封装epoll,将对IO的管理转换为对事件的管理,以及在C语言中实现Reactor模式的关键结构和方法,包括多路复用器、事件分发器和事件处理器。服务端通过接收客户端消息并回发,展示了Reactor模式的实际应用。
摘要由CSDN通过智能技术生成

背景

以前的处理IO的方法都是一个请求一个线程,效率不高

简介

reactor可理解为就是对大量IO进行集中式管理(如用epoll),每个IO走不同的回调函数

Reactor和Proactor是高效的事件处理模型

reactor是对epoll的一层封装 ,epoll是对io进行管理,reactor将对io的管理转化为对事件的管理

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

在这里插入图片描述

Reactor模式是处理并发IO比较常见的一种模式,用于同步IO,中心思想是将所有要处理的IO事件注册到一个中心IO多路复用器上,同时主线程/进程阻塞在多路复用器上;一旦有IO事件到来或是准备就绪(文件描述符或socket可读、写),多路复用器返回并将事先注册的相应IO事件分发到对应的处理器中。

Reactor模型有三个重要的组件:

  • 多路复用器:由操作系统提供,在linux上一般是select, poll, epoll等系统调用。

  • 事件分发器:将多路复用器中返回的就绪事件分到对应的处理函数中。

  • 事件处理器:负责处理特定事件的处理函数(回调函数)。

    在这里插入图片描述

对socket和事件的封装

fd的组织方式是:用nitem封装fd和其对应的读写缓冲区、事件等属性;用定长数组存多个nitem, 再用一个itemblock封装这个数组,从而实现大量存储item,即采用 链表+数组的组织方式。

在这里插入图片描述

相关结构体的具体实现代码如下:

typedef int NCALLBACK(int fd, int event, void *arg);
// 包装fd以及其读写缓冲区和回调
struct nitem {
    int fd;
    int status;
    int events;
    void *arg;

    // 不同的IO对应不同的回调
    NCALLBACK *readcb; // epollin
    NCALLBACK *writecb; // epollout
    NCALLBACK *acceptcb; // epollin

    // 读写缓冲区
    unsigned char sbuffer[BUFFER_LENGTH];
    int slength;
    unsigned char rbuffer[BUFFER_LENGTH];
    int rlength;
};

// !!一个block存多个item,block用链表组织,nitem 也是用链表组织
struct itemblock{
    struct itemblock *next;
    struct nitem *items; // nitem数组
};

struct reactor {
    int epfd;
    struct itemblock *head;
};

服务端接收到客户端发来消息并回发的流程

在这里插入图片描述

C语言完整代码

#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 MAXLINE 4096
#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);

// 包装fd以及其读写缓冲区和回调
struct nitem
{
    int fd;
    int status;
    int events;
    void *arg;

    // 不同的IO对应不同的回调
    NCALLBACK *readcb;   // epollin
    NCALLBACK *writecb;  // epollout
    NCALLBACK *acceptcb; // epollin

    // 读写缓冲区
    unsigned char sbuffer[BUFFER_LENGTH];
    int slength;
    unsigned char rbuffer[BUFFER_LENGTH];
    int rlength;
};

// !!一个block存多个item,block用链表组织,nitem 也是用链表组织
struct itemblock
{
    struct itemblock *next;
    struct nitem *items; // nitem数组
};

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;
// singleton,全局只提供一个reactor访问点
struct reactor *getInstance(void)
{
    if(instance == NULL)
    {
        instance = malloc(sizeof(struct reactor));
        if(instance == NULL)    return NULL;
        memset(instance, 0x00, 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;
    }
    // ptr指向fd对应的nitem
    ev.data.ptr = &r->head->items[fd];

    // socket原来的兴趣事件类型为NOTSET_CB,即初始状态
    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: %d\n", errno);
            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;
}

int read_callback(int fd, int event, void *arg)
{
    struct reactor *R = getInstance();
    unsigned char *buffer = R->head->items[fd].rbuffer;
    // LT
    int ret = recv(fd, buffer, BUFFER_LENGTH, 0);
    if(ret == 0) { // connection closed
        nreactor_del_event(fd, NULL, 0, NULL);
        close(fd);
    }
    else if(ret > 0)
    {
        // echo
        unsigned char *sbuffer = R->head->items[fd].sbuffer;
        memcpy(sbuffer, buffer, ret); 
        R->head->items[fd].slength = ret;

        printf("readcb: %s\n", sbuffer);
        // 对fd注册可写事件,事件到来则执行 write_callback, eventloop中传参
        nreactor_set_event(fd, write_callback, WRITE_CB, NULL);
    }
}

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;
    }
    // 在epoll内核事件表添加connfd的读事件监控
    nreactor_set_event(connfd, read_callback, READ_CB, NULL);
}

// 初始化服务器,在某个IP和PORT进行监听
int init_server(int port)
{
    int listenfd;
    struct sockaddr_in servaddr;
    char buff[MAXLINE];

    if((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
        printf("create socket error: %s(errno: %d)\n", strerror(errno), errno);
        return 0;
    }

    // 设置端口复用
    int opt = 1;
    setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));

    memset(&servaddr, 0x00, 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("listens 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);
    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));

    // ???问题:似乎没有item数组满的时候,增加itemblock节点及其指向的nitem数组的操作
    // 初始化第一个block节点的item数组
    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;
}

// eventloop
int reactor_loop(int listenfd)
{
    struct reactor *R = getInstance();

    struct epoll_event events[MAX_EPOLL_EVENT] = {0};

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

        int i = 0;
        for(int i = 0; i < nready; i++)
        {
            // nreactor_set_event给设置的nitem指针
            struct nitem *item = events[i].data.ptr;
            int connfd = item->fd;

            if(connfd == listenfd) // 监听fd
            {
                // nreactor_set_event中设置的回调函数
                item->acceptcb(listenfd, 0, NULL);
            }
            else // 连接fd
            {
                // 事件被设置为可读时,回调也被一同设置了
                if(events[i].events & EPOLLIN)
                {
                    item->readcb(connfd, 0, NULL);
                }
                // 事件被设置为可写时,回调也被一同设置了
                if(events[i].events & EPOLLOUT)
                {
                    item->writecb(connfd, 0, NULL);
                }
            }
        }
    
    }
}

int main(int argc, char ** argv)
{
    int listenfd = init_server(8888);
    nreactor_set_event(listenfd, accept_callback, ACCEPT_CB, NULL);
    reactor_loop(listenfd);

    return 0;
}
测试

编译

gcc -o reactor_mulcb reactor_mulcb.c

测试

在这里插入图片描述

成功实现回发数据

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Reactor模式是一种事件驱动的设计模式,它的核心思想是将事件处理程序与I/O操作分离开来,通过事件分发器来处理所有的I/O事件,从而提高系统的可扩展性和可维护性。 Reactor模式的实现原理可以分为以下几个步骤: 1. 事件分发器:Reactor模式中的事件分发器负责监听所有的I/O事件,包括读写事件、连接事件和关闭事件等。一旦有事件发生,事件分发器会将事件分发给相应的事件处理程序进行处理。 2. 事件处理程序:事件处理程序是Reactor模式中的核心组件,它负责处理所有的I/O事件。每个事件处理程序都需要实现一个统一的接口,包括处理读写事件、连接事件和关闭事件等。 3. 事件处理器:事件处理器是一种通用的I/O操作处理器,它负责将I/O操作转化为事件,并将事件注册到事件分发器中。在Reactor模式中,所有的I/O操作都需要通过事件处理器进行注册和管理。 4. 多路复用器:多路复用器是一种高效的I/O事件处理机制,它可以同时处理多个I/O事件,从而提高系统的性能和可伸缩性。在Reactor模式中,多路复用器通常被用来监听所有的I/O事件,并将事件分发给相应的事件处理程序进行处理。 总的来说,Reactor模式的实现原理是基于事件驱动的设计思想,通过事件分发器、事件处理程序、事件处理器和多路复用器等组件来实现高效的I/O事件处理机制,从而提高系统的可扩展性和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值