Linux 学习笔记(六十一):网络编程毕业设计——Reactor 反应堆模式

📌 核心结论(TL;DR):

  1. Reactor 模式:一种基于事件驱动的架构,将网络 I/O 就绪监听与业务逻辑解耦。

  2. 三大核心组件 :EventLoop(epoll_wait 循环)、Reactor(分发器)、Handler(处理器)。

  3. 单 Reactor 单线程:适合短连接、业务简单的场景(如 Redis)。

  4. 主从 Reactor 多线程:企业级高并发服务器的标配(如 Netty、Nginx)。

  5. 核心代码思想 :利用 epoll_data 中的 ptr 指针,直接绑定 Connection 对象,实现事件的就绪回调。

一、 为什么需要 Reactor 模式?

如果用原生的 epoll API 写服务器,随着业务逻辑增加,while(1) 里的代码会变得极其臃肿(accept、read、解析协议、计算业务全混在一起)。Reactor 模式就像"智能家居",Reactor 是大脑(时刻监听各个传感器),Connection 是各个智能设备,一旦传感器被触发,大脑就直接调用对应的设备(回调)去工作。

二、 极简 Reactor 代码架构

cpp 复制代码
// 核心思想:把 fd 及其事件处理封装成对象
class Connection {
public:
    std::function<void(Connection*)> onRead; // 业务回调
    void HandleRead() { if(onRead) onRead(this); }
};

class Reactor {
public:
    // 注册时:ev.data.ptr = conn;
    // 循环时:Connection* conn = static_cast<Connection*>(events[i].data.ptr);
    //        conn->HandleRead();
};

通过面向对象封装,epoll 彻底变成了一个底层的"黑盒"引擎。我们只需要往里面添加 Connection 对象,并在对象中注册读、写、异常的回调函数,框架就会在合适的时候调用它们。

三、 主从 Reactor 架构演进

为了榨干多核 CPU 的性能,现代服务器通常采用 主从 Reactor + 线程池 架构:

  1. Main Reactor (1个线程):只负责 accept 新连接。

  2. Sub Reactor(N个线程):将新连接分发给其中一个,负责处理网络 I/O。

  3. Worker 线程池 (M个线程):负责处理复杂的业务逻辑。

    这种设计成功做到了"网络 I/O 不阻塞业务、业务不阻塞网络 I/O",是高并发服务器的终极答案。

完整的 Reactor 框架(比如陈硕大佬的 muduo 库)有上万行代码,包含缓冲区、线程池、定时器等。今天咱们手写一个极简版的面向对象 Reactor (单 Reactor 单线程版),主要体会它的面向对象封装思想。

自我实战操作

bash 复制代码
cd ~/linux_study/network_test
mkdir reactor_demo && cd reactor_demo
实战:极简 Reactor 类封装 reactor.cc

(不用手敲,直接复制,重点看类结构)

cpp 复制代码
#include <iostream>
#include <string>
#include <unordered_map>
#include <functional>
#include <cstring>
#include <unistd.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/epoll.h>

#define PORT 9999
#define MAX_EVENTS 1024

class Reactor;

// 1. 抽象一个 Connection 类
class Connection {
public:
    Connection(int fd, Reactor* reactor) : fd_(fd), reactor_(reactor) {}
    int fd() const { return fd_; }
    
    // 回调函数
    std::function<void(Connection*)> onRead;

    void HandleRead() {
        if (onRead) onRead(this); 
    }
private:
    int fd_;
    Reactor* reactor_;
};

// 2. Reactor 类(封装 epoll)
class Reactor {
public:
    Reactor() { epfd_ = epoll_create(1); }
    ~Reactor() { close(epfd_); }

    void AddConnection(Connection* conn) {
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.ptr = conn; // 绑定对象指针
        epoll_ctl(epfd_, EPOLL_CTL_ADD, conn->fd(), &ev);
        connections_[conn->fd()] = conn;
    }

    void RemoveConnection(Connection* conn) {
        epoll_ctl(epfd_, EPOLL_CTL_DEL, conn->fd(), NULL);
        connections_.erase(conn->fd());
        close(conn->fd());
        delete conn;
    }

    void Loop(int listen_fd) {
        struct epoll_event events[MAX_EVENTS];
        while (true) {
            int nfds = epoll_wait(epfd_, events, MAX_EVENTS, -1);
            for (int i = 0; i < nfds; i++) {
                // 如果就绪的是监听 socket,说明有新连接
                if (events[i].data.fd == listen_fd) {
                    struct sockaddr_in client_addr;
                    socklen_t len = sizeof(client_addr);
                    int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &len);
                    
                    std::cout << "[Reactor] 新连接接入,fd: " << conn_fd << std::endl;
                    
                    // 创建一个新的 Connection 对象,并注册回调函数
                    Connection* conn = new Connection(conn_fd, this);
                    conn->onRead = [](Connection* c) {
                        char buf[1024] = {0};
                        int n = read(c->fd(), buf, sizeof(buf) - 1);
                        if (n > 0) {
                            std::cout << "[收到消息 fd=" << c->fd() << "] " << buf << std::endl;
                            write(c->fd(), buf, n); // Echo 回显
                        } else if (n == 0) {
                            std::cout << "[断开连接 fd=" << c->fd() << "]" << std::endl;
                            // 这里为了简单,先不 delete,实际中需要 c->reactor_->RemoveConnection(c);
                            close(c->fd());
                        }
                    };
                    
                    // 把新连接加入 Reactor
                    AddConnection(conn);
                } else {
                    // 如果是普通连接有数据来了,取出我们绑定的对象指针
                    Connection* conn = static_cast<Connection*>(events[i].data.ptr);
                    if (conn && (events[i].events & EPOLLIN)) {
                        conn->HandleRead(); // 触发回调!
                    }
                }
            }
        }
    }

private:
    int epfd_;
    std::unordered_map<int, Connection*> connections_;
};

// 3. 业务层 (Main)
int main() {
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    int opt = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    addr.sin_addr.s_addr = INADDR_ANY;
    bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr));
    listen(listen_fd, 5);

    Reactor reactor;
    std::cout << "Reactor 服务器启动,监听端口 " << PORT << "..." << std::endl;
    
    // 开启事件循环!
    reactor.Loop(listen_fd);

    return 0;
}

因为用到了 C++ 和 epoll,编译命令如下:

bash

bash 复制代码
g++ reactor.cc -o reactor
./reactor

👉 测试体验 :

开另一个终端,用之前的 tcp_client 连上来,发消息。你会发现,即使没有写长篇大论的 accept 和 read,服务器依然能响应。这就是 Reactor 模式里**"控制反转"**的魅力------框架帮你调用了回调,你只需要管业务逻辑。

相关推荐
2401_8685347812 分钟前
【网络安全
网络·python
科小怪13 分钟前
Obsidian 下载安装:官网下太慢?笔记存自己电脑,不注册不联网
笔记·下载教程·obsidion
xyzzklk14 分钟前
变相解决华为Mate 8锁屏后无法建立入站网络连接
linux·运维·服务器·网络·华为·go·安卓
叼烟扛炮2 小时前
Linux 学习笔记(五十九):epoll 的生死抉择——LT 与 ET 模式
linux·运维·服务器·网络·笔记·学习·php
水云桐程序员9 小时前
从零开发软件的步骤,软件开发从零到完成应该怎么做?
笔记·学习·软件构建·个人开发
David猪大卫9 小时前
【Linux修炼】环境变量、进程地址空间
linux·c语言·经验分享·笔记·学习·ubuntu·centos
叼烟扛炮10 小时前
Linux 学习笔记(五十六):IO 多路复用初探——select 的“三宗罪”
linux·服务器·数据库·笔记·学习·php
Ruiery10 小时前
Linux 6.6内核内存管理深度解析(十五):CMA 连续内存分配器
linux·运维·服务器
xixingzhe210 小时前
AI 对话打字机效果:SSE 和 HTTP 的区别与适用场景
网络·网络协议·http