Reactor模式与单线程Reactor的C和C++实现

参考:

C语言实现简单的Reactor

Reactor模式详解

Reactor 反应堆设计模式

I/O模型与IO多路复用

参见IO模式与IO多路复用

Reactor模式

Reactor模式(反应器模式)是一种处理一个或多个客户端并发交付服务请求的事件设计模式。当请求抵达后,服务处理程序使用I/O多路复用策略,然后同步地派发这些请求至相关的请求处理程序。

Reactor结构

在这里插入图片描述

类图如下
在这里插入图片描述

  • Handle 文件描述符

Handle在Linux中一般称为文件描述符,在Windows中称为句柄,两者含义一样。Handle是事件的发源地。比如网络socket、磁盘文件等。发生在Handle上的事件可以有connectionready for readready for write等。

Handle是操作系统的句柄,是对资源在操作系统上的一种抽象,它可以是打开的文件、一个Socket连接、Timer定时器等。由于Rector模式一般使用在网络编程中,因而这里一般指的是Socket Handle,也就是一个网络连接(connection/channel)。这个channel注册到同步事件分离器中,以监听Handle中发生的事件,对ServerSocketChannel可以是CONNECT事件,对SocketChannel可以是readwriteclose事件等。

  • Synchronous Event Demultiplexer 同步(多路)事件分离器

同步事件分离器本质上是系统调用,比如Linux中的selectpollepoll等。比如select()方法会一致阻塞直到文件描述符handle上有事件发生时才会返回。

无限循环等待新请求的到来,一旦发现有新的事件到来就会通知初始事件分发器去调取特定的时间处理器。

  • Event Handler 事件处理器

事件处理器,定义一些回调方法或称为钩子函数,当handle文件描述符上有事件发生时,回调方法便会执行。供初始事件分发器回调使用。

  • Concrete Event Handler 具体的事件处理器

具体的事件处理器,实现了Event Handler,在回调方法中实现具体的业务逻辑。

  • Initiation Dispatcher 初始事件分发器

初始事件分发器,提供了注册、删除、转发Event Handler的方法。当Synchronous Event Demultiplexer检测到handler上有事件发生时,便会通知initiation dispatcher调用特定的event handler的回调方法。

初始事件分发器用于管理Event Handler,定义注册、移除EventHandler等。它还作为Rector模式的入口调用Synchronous Event Demultiplexer同步多路事件分离器的select方法以阻塞等待事件返回,当阻塞等待返回时,根据事件发生的Handle将其分发给对应的Event Handle事件处理器进行处理,也就是回调EventHandler中的handle_event方法。

在这里插入图片描述

Reactor模式的实现方式

单线程Reactor模式

在这里插入图片描述

Reactor的单线程模式的单线程主要是针对于I/O操作而言,也就是所以的I/O的accept()、read()、write()以及connect()操作都在一个线程上完成的。

但在目前的单线程Reactor模式中,不仅I/O操作在该Reactor线程上,连非I/O的业务操作也在该线程上进行处理了,这可能会大大延迟I/O请求的响应。所以我们应该将非I/O的业务逻辑操作从Reactor线程上卸载,以此来加速Reactor线程对I/O请求的响应。

改进:使用工作者线程池

在这里插入图片描述

与单线程Reactor模式不同的是,添加了一个工作者线程池,并将非I/O操作从Reactor线程中移出转交给工作者线程池来执行。这样能够提高Reactor线程的I/O响应,不至于因为一些耗时的业务逻辑而延迟对后面I/O请求的处理。

I/O操作依旧由一个Reactor来完成,包括I/O的accept()、read()、write()以及connect()操作。
对于一些小容量应用场景,可以使用单线程模型。但是对于高负载、大并发或大数据量的应用场景却不合适,主要原因如下:
① 一个NIO线程同时处理成百上千的链路,性能上无法支撑,即便NIO线程的CPU负荷达到100%,也无法满足海量消息的读取和发送;
② 当NIO线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往会进行重发,这更加重了NIO线程的负载,最终会导致大量消息积压和处理超时,成为系统的性能瓶颈;

多Reactor线程模式

在这里插入图片描述

Reactor线程池中的每一Reactor线程都会有自己的Selector、线程和分发的事件循环逻辑。
mainReactor可以只有一个,但subReactor一般会有多个。mainReactor线程主要负责接收客户端的连接请求,然后将接收到的SocketChannel传递给subReactor,由subReactor来完成和客户端的通信。

所有的I/O操作(包括,I/O的accept()、read()、write()以及connect()操作)依旧还是在Reactor线程(mainReactor线程 或 subReactor线程)中完成的。Thread Pool(线程池)仅用来处理非I/O操作的逻辑。

多Reactor线程模式将“接受客户端的连接请求”和“与该客户端的通信”分在了两个Reactor线程来完成。mainReactor完成接收客户端连接请求的操作,它不负责与客户端的通信,而是将建立好的连接转交给subReactor线程来完成与客户端的通信,这样一来就不会因为read()数据量太大而导致后面的客户端连接请求得不到即时处理的情况。并且多Reactor线程模式在海量的客户端并发请求的情况下,还可以通过实现subReactor线程池来将海量的连接分发给多个subReactor线程,在多核的操作系统中这能大大提升应用的负载和吞吐量。

单线程Reactor模式的C实现

根据 Reactor结构 可以再简化为:

  1. 抽象的事件,直接被具体化为fd
  2. 事件多路分发器, 直接被具体化为epoll
  3. 一个Reactor用来管理整个流程

单线程Reactor模式

GitHub : SimpleReactorWithC

主要函数与结构体说明:

抽象事件结构体

/// 抽象事件
struct myevent_s{
    int fd; // 这里是 连接 socket  必然要保存
    int events; // 记录所关怀的事件类型,读事件还是写事件 必然要保存
    void *arg; // 指向自己的指针 必然要保存
    Callback_t call_back;  // 回调函数 必然要保存
    int status;  // 记录自己是否在epoll树上  1 在epoll树上
    int index;
    char buf[BUFFLEN];  // 一个缓冲区
    int len;  // 缓冲区有效数据的长度
};

int g_efd;  // epoll 实例
struct myevent_s g_events[MAX_EVENTS+1];

initlistensocket:

void initlistensocket(int epfd, unsigned short port)

创建服务监听套接字,构建 抽象事件 (accept 事件)设置其事件回调函数 (acceptConn),并将 抽象事件 添加到epoll的红黑树中进行监控。

eventSet

void eventSet(struct myevent_s *ev, int fd, int events, Callback_t call_back, void * arg, int index)

构建 抽象事件

eventAdd

void eventAdd(int efd, struct myevent_s* ev)

添加抽象事件 到epoll 进行监控

acceptConn

void acceptConn(int fd, int events, void *arg)

accept 触发 处理回调函数,客户端连接成功后创建连接套接字,注意设置为非阻塞

构建 抽象事件 (read事件)并添加到epoll

//设置套接字非阻塞
    int flags = fcntl(fd, F_GETFL, 0);
    fcntl(fd , F_SETFL , flags|O_NONBLOCK);

recvdata

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

read 事件 触发 处理回调函数

接受从客户端传过来的数据并处理

构建 抽象事件 (send事件)并添加到epoll

senddata

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

send 事件 触发 处理回调函数

将处理后的数据 回发给 客户端

完成了一次数据收发,先 删除 epoll 树上的 事件,再构建 抽象事件 (read事件)并添加到epoll

eventDel

void eventDel(int efd, struct myevent_s* ev)

从 epoll 中删除 监听事件

服务端:

GitHub : SimpleReactorWithC

/// 完整代码 见 GitHub
int main()
{
    unsigned short port = 5555;
    /// 创建epoll文件描述符
    /// EPOLL_CLOEXEC标志与open 时的O_CLOEXEC 标志类似,即进程被替换时会关闭文件描述符
    g_efd = epoll_create1(EPOLL_CLOEXEC);
    my_error(g_efd, "epoll_create error");
    /// 创建监听套接字 并 注册 events
    initlistensocket(g_efd, port);
    struct epoll_event events[MAX_EVENTS+1];
    printf("server running:port[%d]\n", port);

    int checkpos = 0, i;
    while(1)
    {
        /// 等待内核 I/O 事件的分发
        int nfd = epoll_wait(g_efd, events, MAX_EVENTS + 1, 1000);
        my_error(nfd, "epoll_wait error");
        /// nfd 分发的事件的个数 这里遍历处理
        for(i = 0; i < nfd; ++i)
        {

            /// 抽象的事件 处理 监听 与 连接 套接字
            struct myevent_s *ev = (struct myevent_s *)events[i].data.ptr;
            /// 回调函数处理事件   回调函数 在 eventSet 注册
            if((events[i].events & EPOLLIN) && (ev->events & EPOLLIN))
                ev->call_back(ev->fd, events[i].events, ev->arg);
            if((events[i].events & EPOLLOUT) && (ev->events & EPOLLOUT))
                ev->call_back(ev->fd, events[i].events, ev->arg);
        }
    }
    return 0;
}

客户端:


#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <stdlib.h>

#define MAXLINE     4096
#define SERV_PORT 5555

int main() {

    int sockfd;
    struct sockaddr_in servaddr;
    // 创建了一个本地套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror( "create socket failed");
    }

    // 初始化目标服务器端的地址, TCP 编程中,使用的是服务器的 IP 地址和端口作为目标
    bzero(&servaddr, sizeof(servaddr));

    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(SERV_PORT);
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);


    // 发起对目标套接字的 connect 调用
    if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) {
        perror("connect failed");
    }

    char send_line[MAXLINE];
    bzero(send_line, MAXLINE);
    char recv_line[MAXLINE];
    bzero(recv_line, MAXLINE);
    // 从标准输入中读取字符串,向服务器端发送
    while (1) {
        bzero(recv_line,MAXLINE); // 注意每次清空

        if (fgets(send_line, MAXLINE, stdin) == NULL)
            break;

        int nbytes = sizeof(send_line);
        if (send(sockfd, send_line, nbytes,0) != nbytes)
            perror("write error");

        bzero(recv_line, MAXLINE); // 注意每次清空
        if (recv(sockfd, recv_line, MAXLINE,0) == 0)
            perror("server terminated prematurely");

        fputs(recv_line, stdout);

    }

    exit(0);
}

在这里插入图片描述

单线程Reactor模式的C++实现

相关类说明

Handler

handler负责抽象事件。

  1. 设置fd
  2. 设置关注的事件类型
  3. 设置回调函数
    等基本事件。

Reactor

reactor负责将handler注册到Synchronous Event Demultiplexer 同步(多路)事件分离器(这里是EpollPoller)上。

EpollPoller

对 epoll 的封装

UpperCharacterServer

创建监听线程,事件回调函数的实现

服务端:

详见: GitHub: SimpleReactorWithC++

在这里插入图片描述

UpperCharacterServer:

//
// Created by leacock on 2020/11/16.
//

#include "UpperCharacterServer.h"

UpperCharacterServer::UpperCharacterServer(int port)
        : port_(port)
        , base_(new Reactor) {
}

UpperCharacterServer::~UpperCharacterServer() {
}


void UpperCharacterServer::start() {
    startup();
    base_->loop();
}

/**
 * 创建 服务端的监听套接字
 * 创建 设置 accpet 事件 到 epoll
 */
void UpperCharacterServer::startup() {

    /// 创建监听套接字
    int lfd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
    my_error(lfd, "socket error");

    /// 创建 accpet handler 实例
    auto lHandler = std::make_shared<Handler>(lfd);
//    auto lHandler = new Handler(lfd);
//    if (handleList_[lfd]!= nullptr){
//        delete(handleList_[lfd]);
//    }
    handleList_[lfd] = lHandler;

    int reuse = 1;
    setsockopt(lHandler->fd(), SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof reuse);
    struct sockaddr_in addr{};
    bzero(&addr, sizeof addr);
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port_);
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    int ret = bind(lHandler->fd(), (sockaddr*)&addr, sizeof addr);
    my_error(ret, "bind error");

    ret = listen(lHandler->fd(), 16);
    my_error(ret, "listen error");

    /// 设置回调函数  accpet 事件的回调 accpetConn
    lHandler->setReadCallback(std::bind(&UpperCharacterServer::accpetConn, this, _1));
    /// 使能 read 事件类型
    lHandler->enableRead();
    /// 注册 描述符到 epoll 中
    base_->registerHandler(lHandler.get());
    //base_->registerHandler(lHandler);

}

/**
 * accept 事件 处理 回调函数
 * @param fd
 */
void UpperCharacterServer::accpetConn(int fd) {

    printf("in accept\n");
    /// 这种方式也可以设置 为 SOCK_NONBLOCK 非阻塞
    int cfd = accept4(fd, NULL, NULL, SOCK_NONBLOCK);
    my_error(cfd, "accept4");
    /// 创建 read handler 实例
    auto connHandler = std::make_shared<Handler>(cfd);
//    auto connHandler = new Handler(cfd);
//    if (handleList_[cfd]!= nullptr){
//        delete(handleList_[cfd]);
//    }
    handleList_[cfd] = connHandler;
    /// 设置回调函数  read 事件的回调 readdata
    connHandler->setReadCallback(std::bind(&UpperCharacterServer::readdata, this, _1));
    /// 使能 read 事件类型
    connHandler->enableRead();
    /// 注册 描述符到 epoll 中
    base_->registerHandler(connHandler.get());
    //base_->registerHandler(connHandler);

}

/**
 * read 事件 处理 回调函数
 * @param fd
 */
void UpperCharacterServer::readdata(int fd) {

    printf("in read\n");
    auto pHandler = handleList_[fd];
    bzero(pHandler->buff(),MAX_BUFFLEN);
    int nbytes = read(fd, pHandler->buff(), MAX_BUFFLEN);
    if(nbytes > 0)
    {
        /// TODO 这里len 可能 == MAX_BUFFLEN
        int len = strlen(pHandler->buff());
        for (int i = 0; i < len; ++i) {
            pHandler->buff()[i] = convert_char(pHandler->buff()[i]);
        }

        printf("%s\n", pHandler->buff());
        pHandler->setLen(len);
        /// 设置回调函数  send 事件的回调 senddata
        pHandler->setWriteCallback(std::bind(&UpperCharacterServer::senddata, this, _1));
        /// 使能 write 事件类型
        pHandler->enableWrite();
        /// 注册 描述符到 epoll 中
        base_->registerHandler(pHandler.get());
        //base_->registerHandler(pHandler);
    }
    else if(nbytes == 0){
        printf("close fd\n");
        close(fd);
        base_->removeHandler(pHandler.get());
        //base_->removeHandler(pHandler);
        handleList_.erase(fd);
    }
    else{
        close(fd);
        printf("read error\n");
        base_->removeHandler(pHandler.get());
        //base_->removeHandler(pHandler);
        handleList_.erase(fd);
    }

}

/**
 * send 事件 处理 回调函数
 * @param fd
 */
void UpperCharacterServer::senddata(int fd) {

    auto pHandler = handleList_[fd];
    int nbytes = write(fd, pHandler->buff(), pHandler->length());
    if(nbytes >= 0)
    {
        pHandler->setReadCallback(std::bind(&UpperCharacterServer::readdata, this, _1));
        pHandler->enableRead();
        base_->registerHandler(pHandler.get());
        //base_->registerHandler(pHandler);
    }
    else {
        printf("write error\n");
        close(fd);
        base_->removeHandler(pHandler.get());
        //base_->removeHandler(pHandler);
        handleList_.erase(fd);
    }

}

char UpperCharacterServer::convert_char(char c) {
    if ( 'A' <= c && c <= 'Z')
        return c + 32; // 转换小写
    else if ( 'a' <= c && c <= 'z')
        return c - 32; // 转换大写
    else
        return c; // 其他不变
}


客户端:

代码同上

在这里插入图片描述

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
【项目介绍】 基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip 基于Reactor的高并发服务器c++和c源码+超详细项目说明+sln解决方案.zip 基于`Reactor`高并发服务器 `C++` > 基于`Reactor`的高并发服务器,分为`反应堆模型`,`多线程`,`I/O模型`,`服务器`,`Http请求`和`响应`五部分 【备注】 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用!有问题请及时沟通。 2、项目适用人群:计科、信息安全、数据科学与大数据技术、人工智能、通信、物联网、自动化、电子信息等计算机相关专业的在校学生、专业老师、行业内企业员工下载使用。 3、项目可用于:项目本身具有较高的学习借鉴价值,不仅适用于小白学习进阶,也可用于专业人员二次开发。当然也可作为毕设项目、课程设计、课程大作业、初期项目立项演示等。 4、如果基础还行,或者热爱钻研,亦可在此项目代码基础上进行修改添加,实现其他不同功能。 欢迎下载使用,相互学习,共同进步!
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
发出的红包

打赏作者

墨1024

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值