Linux高级I/O:Reactor反应堆模式 | Epoll ET模式


全文约 8696 字,预计阅读时长: 25分钟


在这里插入图片描述


Reactor反应堆模式

  • Reactor:负责检测关心的IO事件,当检测到IO事件时,分发给Handlers处理(对数据的读写处理+对数据的分析处理)。
  • epoll ET工作方式:只支持非阻塞IO文件。

简易的Reactor epoll ET TCP服务器

单进程基于ET非阻塞设计的一个Reactor模式+数据读写+数据分析。

socket封装

监听套接字的创建、绑定、监听、设置非阻塞I/O

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <strings.h>
#include<fcntl.h>
using std::cerr;
using std::cin;
using std::cout;
using std::endl;

namespace ns_sock
{
   
   
    enum
    {
   
   
        SOCK_ERR = 2,
        BIND_ERR,
        LISTEN_ERR
    };

    const int g_backlog = 5;

    struct sock_package
    {
   
   
        static int SockCreate()
        {
   
   
            int sock = socket(AF_INET, SOCK_STREAM, 0);
            if (sock < 0)
            {
   
   
                cerr << "socket  failed" << endl;
                exit(SOCK_ERR);
            }
            //设置可以立即重启绑定。
            int opt = 1;
            setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
            return sock;
        }
        static void Bind(const int &sock_fd, const uint16_t &port)
        {
   
   
            struct sockaddr_in local;
            bzero(&local, sizeof(local));
            local.sin_addr.s_addr = INADDR_ANY;
            local.sin_port = htons(port);
            local.sin_family = AF_INET;
            if (bind(sock_fd, (sockaddr *)&local, sizeof(local)) < 0)
            {
   
   
                cerr << "Bind Error" << endl;
                exit(BIND_ERR);
            }
        }
        static void Listen(const int &sock_fd)
        {
   
   
            if (listen(sock_fd, g_backlog) < 0)
            {
   
   
                cerr << "Listen Error" << endl;
                exit(LISTEN_ERR);
            }
        }
        static void set_noblock(int listen_sk)
        {
   
   
            int ret_sock = fcntl(listen_sk,F_GETFL);
            if(ret_sock<0)
            {
   
   
                cerr<<"fcntl error"<<endl;
                exit(5);
            }
            fcntl(listen_sk,F_SETFL,ret_sock | O_NONBLOCK);
        }
    };

}

epoll。hpp

一:epoll文件描述符的创建,往epoll文件描述里、添加需要关心的I/O文件 、以及关心的I/O文件的事件;检测事件的发生。

二:当前I/O文件的错误回调功能具有:哈希表删除当前的fd值,epoll_del从epoll红黑树中删除该fd节点;最后关闭fd。

三:检测到所有的异常事件发生时,都交付给当前I/O文件的错误回调处理。

四:当检测有事件就绪时应检测当前I/O文件的FD是否还存在。因为可能发送事件触发时,上一秒该文件有异常,从epoll中删除了。

五:具有一个可以让:当前I/O文件读取完毕后,构建发送报文时,可以修改当前该文件的关心事件的功能。因为读事件时一直需要关心的,写事件需要服务器返回数据时开启,发送完毕取消关心。sock文件描述符的反应堆指针,是为了后续可以使用其内部相关功能。

#pragma once
#include <iostream>
#include <sys/epoll.h>
#include <unordered_map>
#include <string>
#include <utility>
#include <unistd.h>

using namespace std;
namespace ns_reactor
{
   
   
    class reactor_sv;
    class sock_pack;
    typedef void (*sock_callback)(sock_pack &);//函数指针类型
    class sock_pack//sock_fd配套设施。
    {
   
   
    public
<think>我们正在讨论反应堆模型(Reactor Pattern),这是一种用于处理高并发网络请求的设计模式。根据引用内容,反应堆模型主要解决如何高效处理多个I/O事件的问题,特别是在网络服务器中。 反应堆模型的核心思想:使用一个阻塞调用(如epoll、select)监听多个I/O描述符,当某个描述符就绪(有数据可读、可写或出现异常)时,通知应用程序,然后由应用程序调用预先注册的回调函数处理该事件。 引用中提到的几个关键点: [^3]:反应堆模型通常需要阻塞在一个多路复用函数(如epoll)上,得到就绪描述符后,调用注册的回调函数。 [^4]:Reactor模式是高性能网络服务器的必备技术,具有响应快、避免单个事件阻塞、避免多线程切换开销可扩展性等优点。 [^2]:反应堆需要一个控制块(如mc_event_base_t),其中包含事件列表(如added_list, active_list)epoll文件描述符等。 反应堆模型的基本组成: 1. 事件源(Event Source):即文件描述符(如socket)。 2. 事件多路分离器(Event Demultiplexer):使用系统调用(如epoll_wait)等待事件发生。 3. 事件处理器(Event Handler):为每个事件源注册的处理函数(回调函数)。 4. 反应堆Reactor):核心调度器,负责注册/注销事件处理器,运行事件循环。 工作流程: 1. 初始化反应堆,创建事件多路分离器(如epoll实例)。 2. 注册事件处理器:将事件源(socket对应的事件(读、写等)以及回调函数注册到反应堆。 3. 启动事件循环:调用事件多路分离器(如epoll_wait)等待事件发生。 4. 当某个事件源就绪,事件多路分离器返回,反应堆根据事件类型调用预先注册的回调函数。 5. 回调函数执行实际的I/O操作业务处理。 在系统编程中的应用: 反应堆模型广泛应用于网络服务器,如Nginx、Redis等。它能够以单线程(或少量线程)处理大量并发连接,避免了为每个连接创建线程的开销,提高了系统的可扩展性。 示例代码框架(基于epoll): 以下是一个简化的反应堆模型实现框架(C语言): ```c #include <sys/epoll.h> #include <stdlib.h> // 事件处理器接口 typedef struct event_handler { int fd; // 关联的文件描述符 void (*handle_event)(int fd, uint32_t events); // 事件处理函数 } event_handler_t; // 反应堆控制块 typedef struct reactor { int epoll_fd; struct epoll_event *events; int max_events; // 其他状态信息... } reactor_t; // 初始化反应堆 reactor_t* reactor_init(int max_events) { reactor_t* r = malloc(sizeof(reactor_t)); r->epoll_fd = epoll_create1(0); r->events = calloc(max_events, sizeof(struct epoll_event)); r->max_events = max_events; return r; } // 注册事件处理器 void reactor_register(reactor_t* r, event_handler_t* handler, uint32_t events) { struct epoll_event ev; ev.events = events; ev.data.ptr = handler; // 将处理器指针保存 epoll_ctl(r->epoll_fd, EPOLL_CTL_ADD, handler->fd, &ev); } // 事件循环 void reactor_run(reactor_t* r) { while (1) { int n = epoll_wait(r->epoll_fd, r->events, r->max_events, -1); for (int i = 0; i < n; i++) { struct epoll_event *ev = &r->events[i]; event_handler_t *handler = (event_handler_t*)ev->data.ptr; // 调用事件处理函数 handler->handle_event(handler->fd, ev->events); } } } ``` 在这个框架中,我们定义了一个事件处理器(event_handler_t),其中包含文件描述符事件处理函数。反应堆reactor_t)内部使用epoll来管理事件。当有事件发生时,反应堆epoll_wait返回,然后根据事件关联的事件处理器调用其处理函数。 反应堆模型的优点: 1. 非阻塞:通过事件驱动,避免为每个连接创建线程/进程,减少资源消耗。 2. 可扩展:能够处理大量并发连接(如C10K问题)。 3. 解耦:将事件分发与事件处理分离,便于模块化设计。 缺点: 1. 编程模型复杂:需要异步编程,回调函数可能使代码逻辑碎片化(回调地狱)。 2. 对CPU密集型任务不友好:长时间运行的回调函数会阻塞事件循环。 因此,反应堆模型适合I/O密集型应用,如网络服务器。 相关问题: 1. 反应堆模型生产者-消费者模型有什么区别? 2. 如何解决反应堆模型中回调地狱的问题? 3. 反应堆模型与多线程模型相比,在性能上有什么优劣? 4. 在实际应用中,如何优化反应堆模型以处理更高的并发? 5. 反应堆模型中的事件处理函数如果执行时间过长,会对系统产生什么影响?如何避免?</think>### 反应堆模型详解及其在系统编程中的应用 #### 一、反应堆模型核心概念 **反应堆模型(Reactor Pattern)** 是一种事件驱动的设计模式,用于高效处理大量并发I/O请求。其核心思想是**将I/O事件的检测与事件处理逻辑解耦**,通过事件循环机制调度资源[^3][^4]。 **核心组件**: 1. **事件分离器(Event Demultiplexer)** 使用系统调用(如`epoll`/`select`)监控多个I/O描述符,检测就绪事件: ```c int epoll_wait(epoll_fd, events, max_events, timeout); // Linux实现 ``` 2. **事件处理器(Event Handlers)** 每个I/O描述符注册的回调函数,处理特定事件(如读/写/错误)[^4]。 3. **反应堆核心(Reactor Core)** 调度中枢,管理事件注册与分发: ```c typedef struct mc_event_base_s { void *added_list; // 待注册事件列表 void *active_list; // 活跃事件列表 int epoll_fd; // epoll实例 int ev_base_stop; // 停止标志 } mc_event_base_t; // 引用[2]中的控制块结构 ``` #### 二、工作流程(以epoll为例) ```mermaid graph LR A[启动事件循环] --> B[epoll_wait 阻塞等待] B --> C{有就绪事件?} C -->|| D[遍历就绪事件列表] D --> E[调用对应回调函数] E --> B C -->|超时| F[执行定时任务] F --> B ``` 1. **初始化阶段** 创建epoll实例,初始化事件控制块: ```c mc_event_base_t *base = malloc(sizeof(mc_event_base_t)); base->epoll_fd = epoll_create1(0); // 创建epoll实例 ``` 2. **事件注册** 将socket描述符加入监听列表: ```c struct epoll_event ev; ev.events = EPOLLIN; // 监听读事件 ev.data.fd = socket_fd; epoll_ctl(base->epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev); ``` 3. **事件循环** 核心调度逻辑: ```c while (!base->ev_base_stop) { int n = epoll_wait(base->epoll_fd, events, MAX_EVENTS, -1); for (int i=0; i<n; i++) { EventHandler *handler = find_handler(events[i].data.fd); handler->callback(events[i].events); // 执行回调 } } ``` #### 三、在系统编程中的应用 1. **高性能网络服务器** - 单线程处理10K+并发连接(解决C10K问题) - 避免线程切换开销:相比多线程模型,CPU利用率提升40%+[^4] - 应用案例:Nginx、Redis、Memcached 2. **实时数据处理系统** - 金融交易系统:处理毫秒级市场数据 - 物联网网关:同时管理数万设备连接 3. **操作系统内核组件** - 文件系统事件监听(inotify) - 网络协议栈数据包调度 #### 四、性能优势分析 $$T_{传统} = N \times (T_{thread\_create} + T_{context\_switch})$$ $$T_{Reactor} = T_{epoll} + K \times T_{callback} \quad (K \ll N)$$ | 指标 | 多线程模型 | 反应堆模型 | |---------------|------------------|-------------------| | 内存开销 | 高(每线程MB级) | 低(共享事件表) | | CPU利用率 | 30%-60% | 70%-90%[^4] | | 并发连接数 | 千级 | 百万级 | | 延迟稳定性 | 波动大 | 微秒级抖动 | #### 五、关键实现技术 1. **边缘触发(ET) vs 水平触发(LT)** - ET模式:仅在状态变化时通知(更高性能) - LT模式:状态持续时重复通知(更易编程) 2. **回调函数设计原则** - 非阻塞:避免阻塞事件循环 - 短耗时:单次执行<100μs - 无状态:通过闭包保存上下文 3. **多Reactor扩展** 多核CPU优化方案: ```mermaid graph LR M[Main Reactor] -->|分发连接| S1[Sub Reactor1] M -->|分发连接| S2[Sub Reactor2] S1 --> T1[Worker Threads] S2 --> T2[Worker Threads] ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值