TCP/IP 网络编程 | Reactor事件处理模式
Reactor事件处理模式
文章目录
- Reactor事件处理模式
- 一、什么是Reactor模式
- 1. Reactor概念
- 2. 三大组成部分
- 3. 关键组件解析
- (1). EventHandler(事件处理器)
- (2). EventLoop(事件循环器)
- (3). Acceptor(接受器)
- 4. 工作流程
- 5. 优势与适用场景
- 二、epoll概览
- 1. epoll介绍
- 2. epoll特性
- 2. epoll性能
- 3. ET vs LT模式对比
- (1). 边缘触发模式
- (2). 水平触发模式(默认)
- 三、Reactor和epoll结合
- 1. 模型结构
- 2. 事件处理流程
- 四、完整代码示例
- 1. 核心类设计
- 2. 关键实现细节
一、什么是Reactor模式
1. Reactor概念
Reactor模式是一种基于事件驱动的应用程序组织模型,符合IoC(控制反转)思想:
- 程序不亲自控制IO,而是让Reactor框架进行管理和分发
- 应用程序为各种IO事件注册处理器
- 核心思想:事件驱动 + 单线程 + 非阻塞IO
其核心思想是:
- 单线程事件循环:使用一个线程来处理所有I/O事件
- 事件分离:将事件检测和事件处理分离
- 回调机制:当事件就绪时,调用相应的处理函数
2. 三大组成部分
- Reactor(反应器): 主管执行IO处理循环,监听IO事件
- Handler(处理器): 响应特定事件的处理程序
- Synchronous Event Demultiplexer(同步事件分离器): 如select/poll/epoll
3. 关键组件解析
(1). EventHandler(事件处理器)
class EventHandler {virtual void handleRead() {}virtual void handleWrite() {}virtual void handleError() {}
};
这是策略模式的应用,不同类型的事件有不同的处理策略。
(2). EventLoop(事件循环器)
这是Reactor的核心,负责:
- 使用epoll监控文件描述符
- 分发事件到对应的处理器
- 管理事件处理器的生命周期
(3). Acceptor(接受器)
专门处理新连接的建立,体现了单一职责原则。
4. 工作流程
- Reactor调用IO系统API (如epoll)监听fd
- IO事件发生
- Reactor调用应用注册的Handler处理
- Handler处理完成后,控制权返回Reactor
- 循环继续
5. 优势与适用场景
对比:
特性 | 传统多线程 | Reactor模式 |
---|---|---|
线程数量 | 每连接一线程单线程 | 处理所有连接 |
内存消耗 | 高(每线程~8MB栈) | 低 |
上下文切换 | 频繁 | 无 |
同步问题 | 复杂(锁、条件变量) | 简单 |
扩展性 | 受线程数限制 | 受单机性能限制 |
优势:
- 高并发性能:单线程处理大量连接,避免线程切换开销
- 内存效率:相比多线程模型,内存占用更少
- 可扩展性:易于添加新的事件类型和处理器
适用场景:
- 高并发网络服务器
- I/O密集型应用
- 需要处理大量短连接的场景
二、epoll概览
详见 TCP/IP 网络编程 | IO多路复用select/poll/epoll
含具体代码
1. epoll介绍
epoll
是Linux上最高效的IO多路处理技术,目前基本可看作select/poll的升级版本。
// 创建epoll实例
int epoll_create1(int flags);// 控制epoll行为
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
// op: EPOLL_CTL_ADD, EPOLL_CTL_MOD, EPOLL_CTL_DEL// 等待事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
关键API:
epoll_create
:创建epoll对象epoll_ctl
:将fd注册到epoll对象epoll_wait
:等待事件发生
epoll_event结构体:
struct epoll_event {uint32_t events; // 事件类型epoll_data_t data; // 用户数据
};typedef union epoll_data {void *ptr;int fd;uint32_t u32;uint64_t u64;
} epoll_data_t;
2. epoll特性
- 支持事件规则:EPOLLIN / EPOLLOUT / EPOLLERR
- 支持 ET (Edge Triggered) 和 LT (Level Triggered)
- 构造事件结构
epoll_event {uint32_t events; void* data;}
2. epoll性能
- 时间复杂度: O(1) - 与监控的fd数量无关
- 空间复杂度: O(n) - n为监控的fd数量
- 内核实现: 红黑树 + 就绪链表
3. ET vs LT模式对比
(1). 边缘触发模式
// 特点:只在状态变化时触发一次
// 优点:性能更高,系统调用更少
// 缺点:必须一次性处理完所有数据while (true) {int n = epoll_wait(epfd, events, MAX_EVENTS, -1);for (int i = 0; i < n; i++) {if (events[i].events & EPOLLIN) {// 必须循环读取直到EAGAINwhile (true) {char buf[1024];int ret = read(events[i].data.fd, buf, 1024);if (ret < 0) {if (errno == EAGAIN || errno == EWOULDBLOCK) {break; // 数据读完了}// 处理错误} else if (ret == 0) {// 连接关闭break;}// 处理数据}}}
}
- 只在状态变化时触发一次
- 需要一次性读取/写入所有数据
- 性能更高,但编程复杂度较大
(2). 水平触发模式(默认)
// 特点:只要条件满足就持续触发
// 优点:编程简单,不容易丢失事件
// 缺点:可能产生多余的系统调用while (true) {int n = epoll_wait(epfd, events, MAX_EVENTS, -1);for (int i = 0; i < n; i++) {if (events[i].events & EPOLLIN) {// 即使只读取部分数据,下次epoll_wait仍会返回该事件char buf[100];read(events[i].data.fd, buf, 100);}}
}
- 只要条件满足就持续触发
- 编程简单,但可能产生多余的系统调用
三、Reactor和epoll结合
Reactor模式体现在Linux中,就是一个主循环用 epoll_wait
加上各类fd的处理器:
- 用 epoll 实现底层 IO事件监听
- 用 Reactor 分离事件和处理逻辑
1. 模型结构
+------------------+| Reactor || epoll_wait()、分发 |+------------------+|+--------+--------+| |+--------v-----+ +------v--------+| AcceptHandler| | IOHandler || (接口连接) | | (读写数据) |+--------------+ +---------------+
2. 事件处理流程
- 主线程创建epoll实例
- 将listen_fd注册到epoll(EPOLLIN事件)
- 进入事件循环:epoll_wait()
- 当有新连接到达:
- AcceptHandler处理accept()
- 将新的client_fd注册到epoll(EPOLLIN事件)
- 当有数据可读:
- IOHandler处理read()
- 根据需要修改为EPOLLOUT事件
- 当可写时:
- IOHandler处理write()
- 写完后重新注册EPOLLIN事件
- 循环继续
四、完整代码示例
1. 核心类设计
EventHandler基类
class EventHandler {
public:virtual ~EventHandler() = default;virtual void handleRead() {} // 可读事件处理virtual void handleWrite() {} // 可写事件处理virtual void handleError() {} // 错误事件处理virtual int getFd() const = 0; // 获取文件描述符
};
EventLoop核心实现
class EventLoop {
private:int epollFd_; // epoll文件描述符std::unordered_map<int, std::shared_ptr<EventHandler>> handlers_; // fd到处理器的映射bool running_; // 运行状态public:void addEvent(int fd, EventType events, std::shared_ptr<EventHandler> handler);void modifyEvent(int fd, EventType events);void removeEvent(int fd);void run(); // 主事件循环
};
2. 关键实现细节
非阻塞设置
// 将socket设置为非阻塞模式
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
边缘触发的正确处理
void ConnectionHandler::handleRead() override {char buffer[1024];ssize_t n = read(fd_, buffer, sizeof(buffer));if (n > 0) {// 处理数据} else if (n == 0) {// 连接关闭} else {// n < 0if (errno != EAGAIN && errno != EWOULDBLOCK) {// 真正的错误handleError();}// EAGAIN表示暂时没有数据,正常情况}
}