
1. 项目概述为什么我们需要Reactor模式如果你写过C的网络服务器尤其是那种需要同时处理成百上千个客户端连接的肯定对“C10K问题”不陌生。传统的多线程模型一个连接一个线程当连接数上来后线程上下文切换的开销、内存占用每个线程都有独立的栈会直接把服务器压垮。这时候事件驱动模型就成了救星而Reactor模式就是实现事件驱动架构最经典、最核心的设计模式之一。简单来说Reactor模式的核心思想就一句话“别让程序傻等有事儿叫你”。它用一个中心化的“反应堆”Reactor来监听所有的事件源比如网络套接字当某个事件源就绪比如有数据可读、连接可写时Reactor就通知对应的处理器Handler去处理。整个过程是同步的、非阻塞的但通过高效的I/O多路复用技术如epoll,kqueue实现了高并发。我最初接触Reactor是在做游戏服务器的时候当时用原生的select写感觉代码又乱又难维护。后来研究了像Muduo、libevent这些开源库才恍然大悟原来优雅的高并发代码应该这么写。所以今天我就带大家从零开始手搓一个简单的C Reactor。我们不依赖任何第三方网络库就用最朴素的Linux系统调用和C标准库把Reactor的核心骨架搭起来。目标是让你看完就能理解其原理并且有能力根据自己的业务需求进行扩展。2. Reactor模式的核心架构拆解在动手写代码之前我们必须把Reactor的“五脏六腑”搞清楚。它不是一个黑盒子而是一套精密的协作机制。2.1 核心组件与好莱坞原则Reactor模式通常包含以下几个关键角色Handle句柄 这是事件源的抽象表示。在Linux网络编程里最常用的Handle就是文件描述符File Descriptor, fd特别是socket fd。一个Handle代表一个可以进行I/O操作的对象。Synchronous Event Demultiplexer同步事件分离器 这个名字听起来很高大上其实它就是操作系统提供的I/O多路复用函数比如select、poll、epollLinux、kqueueBSD/macOS。它的工作就是帮我们“盯着”一大堆Handle阻塞地等待其中任何一个或多个发生我们感兴趣的事件比如可读、可写一旦有事件发生它就返回并告诉我们哪些Handle“就绪”了。Event Handler事件处理器接口 这是一个抽象基类定义了处理各种事件的接口比如handle_read、handle_write、handle_error。它是应用程序业务逻辑的接入点。Concrete Event Handler具体事件处理器 实现了Event Handler接口的类。每个具体的处理器负责处理一种或一类事件。比如一个EchoHandler专门处理回显业务一个HttpHandler专门解析HTTP协议。Reactor反应器 整个模式的大脑和调度中心。它主要干三件事注册/注销 提供接口让Concrete Event Handler将自己和它关心的Handle及事件类型注册进来。事件循环 在一个无限循环中调用Synchronous Event Demultiplexer等待事件发生。分发 当分离器返回后Reactor遍历就绪的Handle找到每个Handle对应的Concrete Event Handler并调用其相应的处理接口如handle_read。这里有一个非常重要的设计思想“好莱坞原则”Hollywood Principle即“别打电话给我们我们会打给你”。在传统编程中应用程序主动调用函数来获取事件忙轮询。而在Reactor中应用程序将处理逻辑注册给Reactor后就“躺平”了控制权反转Inversion of Control, IoC给了Reactor框架。是Reactor在事件发生时“回调”你的处理器。这极大地解耦了事件监测和事件处理逻辑。2.2 为何选择Reactor对比其他模型为了更清楚Reactor的价值我们把它和另外两种常见的服务器模型放在一起对比一下模型工作原理优点缺点适用场景迭代/阻塞式单线程accept()后read()阻塞等待。处理完一个再处理下一个。模型最简单代码直观。性能极差完全无法并发资源利用率低。仅用于理解概念无实际生产价值。多进程/多线程每到来一个连接就创建一个新的进程或线程来专门处理它。编程模型相对简单仿佛在写阻塞式代码能利用多核。1. 资源消耗大进程/线程本身占用内存。2. 上下文切换开销大连接数高时C10K性能急剧下降。3. 同步与资源共享复杂。连接数不多如1000且连接生命周期内计算任务较重的场景。Reactor事件驱动单线程或少量线程通过I/O多路复用监听所有连接事件就绪后分发给对应的回调函数处理。1.高并发单线程即可处理数万连接资源占用少。2.高性能避免不必要的上下文切换CPU时间片用在真正干活的地方。3.模块化清晰事件处理逻辑被封装在独立的Handler中。1. 编程模型复杂回调函数Callback风格需要适应。2. 所有Handler在同一个线程执行某个Handler如果阻塞如进行大量CPU计算或阻塞IO会拖垮整个Reactor。3. 调试困难执行流由事件驱动非线性。I/O密集型应用如高性能Web服务器、API网关、即时通讯服务器、游戏服务器。连接数高但单个连接上的业务处理轻量且快速。从表格可以看出Reactor用编程复杂性换来了极高的I/O处理效率这正是现代网络服务器最需要的特质。注意 我们常说的“单线程Reactor”指的是I/O操作和事件分发在一个线程。对于计算密集型任务常见的做法是引入“线程池”Reactor线程只负责快速的I/O读写然后将需要复杂计算的任务包扔给后台线程池处理计算完成后再通过事件通知回来。这就是Reactor ThreadPool的变体今天我们实现最基础的单线程版本。3. 从零开始实现一个简易Reactor框架理论说再多不如一行代码。接下来我们抛开POCO、Boost.Asio这些重型框架用大约300行C代码实现一个最精简但五脏俱全的Reactor。3.1 底层基石选择事件分离器epoll在Linux下我们有select,poll,epoll三种主要选择。select和poll在监听大量fd时需要每次都将完整的fd集合从用户态拷贝到内核态效率低。epoll使用了“事件表”的概念只需一次初始化后续通过epoll_ctl增删改兴趣事件epoll_wait只返回就绪的事件效率极高是当今高性能服务器的标配。所以我们的Reactor核心将基于epoll实现。这里简单回顾一下epoll的三个关键系统调用int epoll_create1(int flags): 创建一个epoll实例返回一个文件描述符epfd。int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event): 管理兴趣列表。op:EPOLL_CTL_ADD添加EPOLL_CTL_MOD修改EPOLL_CTL_DEL删除。event: 指向epoll_event结构体的指针里面包含了我们关心的fd和事件类型EPOLLIN可读EPOLLOUT可写等。int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout): 等待事件发生。events是一个数组用于接收就绪的事件。timeout为-1表示阻塞等待。3.2 核心类设计我们的迷你Reactor将包含以下几个类EventLoop: 这就是我们的Reactor负责驱动整个事件循环。Channel: 这是核心抽象一个Channel对象负责管理一个fd的生命周期和所有感兴趣的事件及其回调。它是对epoll_event和回调函数的封装。Poller: 对epoll系统调用的封装类EventLoop会持有一个Poller对象。这样设计是为了隔离底层IO多路复用实现未来如果想换用poll或kqueue只需修改Poller的实现。Acceptor: 用于处理监听socketlistenfd上的EPOLLIN事件即接受新连接。它是一个特殊的Channel。TcpConnection: 代表一个已建立的TCP连接封装了对端的socket fd以及在该连接上可能发生的读、写、关闭、错误等事件的处理回调。我们先从最基础的Channel和Poller开始。// Channel.h #ifndef SIMPLE_REACTOR_CHANNEL_H #define SIMPLE_REACTOR_CHANNEL_H #include functional #include memory class EventLoop; // 前向声明 class Channel { public: using EventCallback std::functionvoid(); Channel(EventLoop* loop, int fd); ~Channel(); // 处理事件由EventLoop在事件循环中调用 void handleEvent(); // 设置各类事件的回调函数 void setReadCallback(EventCallback cb) { readCallback_ std::move(cb); } void setWriteCallback(EventCallback cb) { writeCallback_ std::move(cb); } void setErrorCallback(EventCallback cb) { errorCallback_ std::move(cb); } void setCloseCallback(EventCallback cb) { closeCallback_ std::move(cb); } // 获取fd和当前关心的事件 int fd() const { return fd_; } int events() const { return events_; } int revents() const { return revents_; } // 由Poller设置表示实际发生的事件 // 设置/更新当前关心的事件并通知EventLoop更新Poller中的注册信息 void enableReading() { events_ | kReadEvent; update(); } void disableReading() { events_ ~kReadEvent; update(); } void enableWriting() { events_ | kWriteEvent; update(); } void disableWriting() { events_ ~kWriteEvent; update(); } void disableAll() { events_ kNoneEvent; update(); } // 用于Poller判断当前Channel的状态 bool isNoneEvent() const { return events_ kNoneEvent; } bool isWriting() const { return events_ kWriteEvent; } bool isReading() const { return events_ kReadEvent; } // 由Poller调用设置实际发生的事件 void set_revents(int revt) { revents_ revt; } // 所属的EventLoop EventLoop* ownerLoop() { return loop_; } private: void update(); // 通知EventLoop更新Channel在Poller中的注册状态 static const int kNoneEvent; static const int kReadEvent; static const int kWriteEvent; EventLoop* loop_; // 所属的EventLoop const int fd_; // Channel负责的文件描述符生命周期不归Channel管 int events_; // 它关心的事件由用户设置EPOLLIN, EPOLLOUT等 int revents_; // 当前活动的事件由Poller/poll/epoll_wait等设置 EventCallback readCallback_; EventCallback writeCallback_; EventCallback errorCallback_; EventCallback closeCallback_; }; #endif //SIMPLE_REACTOR_CHANNEL_HChannel类的设计精髓在于它将一个fd、这个fd上关心的事件events_、实际发生的事件revents_以及对应事件的回调函数绑定在了一起。EventLoop通过Poller拿到一堆就绪的epoll_event后需要找到对应的Channel并调用其handleEvent()方法而Channel自己就知道该调用哪个回调。// Poller.h (抽象基类) 和 EPollPoller.h (具体实现) // Poller.h #ifndef SIMPLE_REACTOR_POLLER_H #define SIMPLE_REACTOR_POLLER_H #include vector #include unordered_map #include memory class Channel; class EventLoop; // EventLoop拥有Poller class Poller { public: using ChannelList std::vectorChannel*; Poller(EventLoop* loop); virtual ~Poller() default; // 所有Poller派生类必须实现的接口 virtual void poll(int timeoutMs, ChannelList* activeChannels) 0; virtual void updateChannel(Channel* channel) 0; virtual void removeChannel(Channel* channel) 0; // 判断参数channel是否在当前Poller的监视列表中 virtual bool hasChannel(Channel* channel) const; protected: // fd - Channel* 的映射方便根据fd快速找到Channel对象 using ChannelMap std::unordered_mapint, Channel*; ChannelMap channels_; private: EventLoop* ownerLoop_; }; #endif //SIMPLE_REACTOR_POLLER_H// EPollPoller.h #ifndef SIMPLE_REACTOR_EPOLLPOLLER_H #define SIMPLE_REACTOR_EPOLLPOLLER_H #include Poller.h #include sys/epoll.h #include vector class EPollPoller : public Poller { public: EPollPoller(EventLoop* loop); ~EPollPoller() override; void poll(int timeoutMs, ChannelList* activeChannels) override; void updateChannel(Channel* channel) override; void removeChannel(Channel* channel) override; private: static const int kInitEventListSize 16; // 初始事件列表大小 // 将epoll_event中的事件转换为Channel中定义的事件标志以及反向转换 static int eventsToEpoll(Channel::EventType event); static int epollToEvents(int epollEvent); void fillActiveChannels(int numEvents, ChannelList* activeChannels) const; void update(int operation, Channel* channel); int epollfd_; // epoll实例的文件描述符 // 用于存放epoll_wait返回的就绪事件 std::vectorstruct epoll_event events_; }; #endif //SIMPLE_REACTOR_EPOLLPOLLER_HEPollPoller是Poller的epoll实现。它维护了epollfd_和events_列表。poll方法就是对epoll_wait的封装它将返回的就绪事件转换为Channel列表。updateChannel和removeChannel则是对epoll_ctl的封装。3.3 大脑EventLoop的实现EventLoop是Reactor模式的“循环”本身也是整个框架的调度中心。// EventLoop.h #ifndef SIMPLE_REACTOR_EVENTLOOP_H #define SIMPLE_REACTOR_EVENTLOOP_H #include atomic #include memory #include vector #include mutex #include functional #include Poller.h #include Channel.h class EventLoop { public: using Functor std::functionvoid(); EventLoop(); ~EventLoop(); // 开始事件循环 void loop(); // 退出事件循环 void quit(); // 在当前Loop所属的线程中执行回调cb线程安全 void runInLoop(Functor cb); // 将回调cb放入队列在Loop下一次迭代时执行线程安全 void queueInLoop(Functor cb); // 内部使用用于唤醒Loop例如当有新的回调任务加入时 void wakeup(); // 更新Channel在Poller中的状态 void updateChannel(Channel* channel); void removeChannel(Channel* channel); // 判断当前线程是否是创建此EventLoop的线程 bool isInLoopThread() const { return threadId_ std::this_thread::get_id(); } private: void handleWakeup(); // 处理wakeup事件 void doPendingFunctors(); // 执行队列中的回调函数 std::atomic_bool looping_; // 是否处于事件循环中 std::atomic_bool quit_; // 是否退出标志 const std::thread::id threadId_; // 创建该Loop的线程ID std::unique_ptrPoller poller_; // 持有的Poller对象 std::unique_ptrChannel wakeupChannel_; // 用于唤醒的Channel // 活跃Channel列表由poller-poll()填充 ChannelList activeChannels_; // 跨线程调用的回调函数队列 std::vectorFunctor pendingFunctors_; std::mutex mutex_; // 保护pendingFunctors_的互斥锁 }; #endif //SIMPLE_REACTOR_EVENTLOOP_HEventLoop的核心是loop()函数它是一个典型的事件循环// EventLoop.cpp (核心部分) void EventLoop::loop() { looping_ true; quit_ false; while (!quit_) { activeChannels_.clear(); // 1. 通过Poller获取就绪的Channel poller_-poll(kPollTimeMs, activeChannels_); // 2. 遍历就绪Channel列表调用其handleEvent() for (Channel* channel : activeChannels_) { channel-handleEvent(); } // 3. 执行其他线程投递过来的回调任务例如在计算线程中完成计算后通知IO线程发送数据 doPendingFunctors(); } looping_ false; }这个循环清晰体现了Reactor的工作流程等待事件 - 分发事件 - 处理事件 - 处理其他任务 - 继续等待。关键技巧wakeup与跨线程调用你可能会问如果EventLoop阻塞在poller_-poll()里其他线程怎么通知它去做别的事情比如执行一个回调这就是wakeupChannel_的作用。我们在创建EventLoop时会创建一个eventfd或者一对pipe作为唤醒fd并将其封装成一个Channel注册到Poller中监听可读事件。当其他线程调用queueInLoop()时除了将回调加入队列还会向这个eventfd写一个字节。这样阻塞在epoll_wait的EventLoop线程就会被唤醒然后handleWakeup()读掉这个字节接着执行doPendingFunctors()从而安全地在IO线程执行了其他线程投递的任务。这是实现“one loop per thread”模型下线程间通信的经典手法。4. 构建上层建筑Acceptor与TcpConnection有了底层的EventLoop和Channel我们就可以构建更上层的网络抽象了。4.1 Acceptor迎接新连接Acceptor负责监听套接字接受新连接。它本身就是一个Channel监听EPOLLIN事件有新连接到来。// Acceptor.h #ifndef SIMPLE_REACTOR_ACCEPTOR_H #define SIMPLE_REACTOR_ACCEPTOR_H #include functional #include Channel.h class EventLoop; class InetAddress; // 封装IP和端口 class Acceptor { public: using NewConnectionCallback std::functionvoid(int sockfd, const InetAddress); Acceptor(EventLoop* loop, const InetAddress listenAddr); ~Acceptor(); void setNewConnectionCallback(const NewConnectionCallback cb) { newConnectionCallback_ cb; } bool listening() const { return listening_; } void listen(); private: void handleRead(); // Channel的读事件回调实际是accept新连接 EventLoop* loop_; int listenfd_; // 监听套接字 std::unique_ptrChannel acceptChannel_; // 监听套接字对应的Channel NewConnectionCallback newConnectionCallback_; // 新连接到来时的回调 bool listening_; }; #endif //SIMPLE_REACTOR_ACCEPTOR_HAcceptor的实现中handleRead()会调用accept(2)系统调用获取一个新的连接套接字connfd然后调用newConnectionCallback_。这个回调函数由TcpServer设置用于创建和管理新的TcpConnection。4.2 TcpConnection连接的生命周期管理者TcpConnection可能是业务逻辑最集中的地方。它代表一个完整的TCP连接从建立到销毁。// TcpConnection.h (简化版) #ifndef SIMPLE_REACTOR_TCPCONNECTION_H #define SIMPLE_REACTOR_TCPCONNECTION_H #include memory #include string #include atomic #include Channel.h #include Buffer.h // 自定义的缓冲区类用于处理读写 class EventLoop; class Socket; // 封装socket fd的生命周期RAII class TcpConnection : public std::enable_shared_from_thisTcpConnection { public: using Pointer std::shared_ptrTcpConnection; using MessageCallback std::functionvoid (const TcpConnection::Pointer, Buffer*, Timestamp); // Timestamp为时间戳 using CloseCallback std::functionvoid (const TcpConnection::Pointer); TcpConnection(EventLoop* loop, const std::string name, int sockfd, const InetAddress localAddr, const InetAddress peerAddr); ~TcpConnection(); EventLoop* getLoop() const { return loop_; } const std::string name() const { return name_; } // 发送数据线程安全 void send(const std::string message); // 关闭连接线程安全 void shutdown(); // 设置各种回调 void setMessageCallback(const MessageCallback cb) { messageCallback_ cb; } void setCloseCallback(const CloseCallback cb) { closeCallback_ cb; } // 连接建立/销毁时调用 void connectEstablished(); // 在Acceptor接受连接后调用 void connectDestroyed(); // 在连接关闭时调用 private: // Channel的事件回调 void handleRead(Timestamp receiveTime); void handleWrite(); void handleClose(); void handleError(); // 内部发送函数在IO线程中执行 void sendInLoop(const void* message, size_t len); // 内部关闭函数 void shutdownInLoop(); enum StateE { kConnecting, kConnected, kDisconnecting, kDisconnected }; void setState(StateE s) { state_ s; } EventLoop* loop_; // 所属的EventLoop即IO线程 const std::string name_; std::atomicStateE state_; std::unique_ptrSocket socket_; std::unique_ptrChannel channel_; const InetAddress localAddr_; const InetAddress peerAddr_; MessageCallback messageCallback_; // 收到消息的回调 CloseCallback closeCallback_; // 连接关闭的回调 Buffer inputBuffer_; // 应用层接收缓冲区 Buffer outputBuffer_; // 应用层发送缓冲区 }; #endif //SIMPLE_REACTOR_TCPCONNECTION_HTcpConnection的关键在于状态管理和缓冲区。状态管理kConnecting正在连接、kConnected已连接、kDisconnecting正在关闭、kDisconnected已断开。状态的正确转换是保证资源安全释放的基础。缓冲区这是实现高性能非阻塞网络编程的灵魂。当调用send()时如果TCP内核发送缓冲区已满非阻塞写返回EAGAIN我们不能阻塞而是应该将剩余数据存入outputBuffer_并监听该fd的EPOLLOUT事件。当内核缓冲区有空闲时epoll会通知我们我们在handleWrite()中继续发送outputBuffer_里的数据。接收数据同理handleRead()一次性尽可能多读存入inputBuffer_再由应用层回调messageCallback_来解析和处理。这避免了为每个连接分配固定大小的缓冲区可能造成的浪费或不足。4.3 最后的拼图TcpServerTcpServer是给用户使用的、最上层的类。它组合了Acceptor和TcpConnection的管理。// TcpServer.h #ifndef SIMPLE_REACTOR_TCPSERVER_H #define SIMPLE_REACTOR_TCPSERVER_H #include memory #include unordered_map #include Acceptor.h #include EventLoop.h #include TcpConnection.h class TcpServer { public: TcpServer(EventLoop* loop, const InetAddress listenAddr); ~TcpServer(); void start(); // 设置回调非线程安全需在start()前调用 void setConnectionCallback(const TcpConnection::ConnectionCallback cb) { connectionCallback_ cb; } void setMessageCallback(const TcpConnection::MessageCallback cb) { messageCallback_ cb; } private: void newConnection(int sockfd, const InetAddress peerAddr); void removeConnection(const TcpConnection::Pointer conn); void removeConnectionInLoop(const TcpConnection::Pointer conn); using ConnectionMap std::unordered_mapstd::string, TcpConnection::Pointer; EventLoop* loop_; // 基础循环通常称为mainLoop或acceptorLoop const std::string name_; std::unique_ptrAcceptor acceptor_; ConnectionMap connections_; // 当前所有存活的连接 TcpConnection::ConnectionCallback connectionCallback_; TcpConnection::MessageCallback messageCallback_; }; #endif //SIMPLE_REACTOR_TCPSERVER_HTcpServer的工作流程是在构造函数中创建Acceptor并设置其newConnectionCallback_为TcpServer::newConnection。用户调用start()让Acceptor开始监听。当新连接到来Acceptor回调newConnection。在这个函数里TcpServer会用connfd创建一个TcpConnection对象设置好各种回调如messageCallback_并调用connectEstablished()将其Channel注册到EventLoop中。当连接关闭时TcpConnection会回调removeConnection由TcpServer在创建连接时设置TcpServer将其从connections_映射中移除。至此一个简易但完整的Reactor模式C网络库骨架就搭建完成了。用户只需要继承TcpServer或者直接设置messageCallback等就可以实现自己的业务逻辑例如一个Echo服务器或者一个简单的HTTP服务器。5. 避坑指南与性能调优实战纸上得来终觉浅绝知此事要躬行。在实现和使用Reactor的过程中我踩过不少坑这里分享几个最关键的。5.1 线程模型与One Loop Per Thread我们上面实现的是单Reactor单线程模型所有I/O和处理都在一个线程。这对于计算轻量的服务足够了但如果业务逻辑复杂会阻塞事件循环。更成熟的模型是单Reactor多线程主线程main Reactor只负责accept新连接然后将新连接分发给子线程sub Reactor。每个子线程运行一个独立的EventLoop负责一组连接的读写和业务处理。这就是Muduo库采用的one loop per thread模型。实现关键创建一个EventLoopThreadPool事件循环线程池。TcpServer持有这个线程池。在newConnection中通过轮询或哈希算法从线程池中选取一个EventLoop子线程将新创建的TcpConnection对象转移到那个线程中去管理通过runInLoop接口。这意味着一个连接的所有事件回调都会在它所属的那个固定IO线程中被调用天然避免了并发访问的锁竞争。5.2 缓冲区Buffer的设计艺术缓冲区是高性能网络编程的基石设计不好会成为性能瓶颈或Bug之源。内部结构通常采用vectorchar作为底层容器但会维护三个索引readIndex可读数据起始、writeIndex可写空间起始、prependableIndex预留空间起始用于在头部添加数据如协议头。这种设计避免了频繁的内存搬移。扩容策略当写入数据时空间不足是原地扩容还是重新分配通常采用“预留空间”策略每次分配比实际需要更大的内存如2倍增长减少realloc或memcpy的次数。粘包拆包Buffer只负责存储字节流不负责解析消息边界。应用层的messageCallback需要根据协议如固定长度、分隔符、TLV格式等从inputBuffer_中取出一个完整的应用层消息包进行处理。这是网络编程的核心业务逻辑必须与Buffer解耦。5.3 资源管理与对象生命周期这是C网络编程中最容易出错的地方尤其是多线程环境下。TcpConnection的生命期必须使用shared_ptr管理。因为一个连接可能同时在多个地方被引用例如在ConnectionMap中在某个定时器任务中在某个待发送的消息队列中。使用shared_ptr可以保证对象在最后一个引用消失时才被销毁。TcpConnection继承enable_shared_from_this就是为了能在其成员函数中安全地获取指向自身的shared_ptr并传递给其他可能延长其生命期的回调。Channel的生命期Channel的生命期通常短于其管理的fd但长于一次事件回调。它由TcpConnection或Acceptor等所有者以unique_ptr形式持有。绝对要避免在Channel的事件回调函数如handleRead中直接delete this或导致所有者析构这会造成悬空指针。正确的做法是在handleClose中通知上层所有者如TcpServer由上层来发起连接对象的销毁流程。跨线程回调与队列EventLoop::runInLoop和queueInLoop是线程安全的桥梁。任何需要跨线程执行的操作都应该封装成Functor通过这两个接口投递到目标IO线程的待执行队列中。执行时IO线程会依次调用这些函数。这保证了所有对Channel、TcpConnection等对象的修改都在其所属的IO线程中串行进行无需加锁。5.4 常见问题排查表在实际开发中你可能会遇到以下问题现象可能原因排查思路与解决方案CPU占用100%事件循环空转epoll_wait立即返回。检查是否有某个fd一直处于就绪状态但未被正确处理如一直可读但没读走数据。检查wakeupChannel是否被错误地持续触发。使用strace跟踪系统调用或使用perf工具分析热点。内存缓慢增长连接关闭后资源未释放内存泄漏。1. 检查TcpConnection的shared_ptr引用计数是否在连接关闭后正确归零。2. 检查Channel是否从Poller中正确移除epoll_ctl(EPOLL_CTL_DEL)。3. 使用Valgrind或AddressSanitizer进行内存检测。连接数上去后性能下降1.epoll事件集合过大每次epoll_wait遍历开销大。2. 某个Handler处理过慢阻塞事件循环。3. 锁竞争激烈。1. 确认使用的是epoll而非select/poll。2. 使用one loop per thread模型分散负载。3. 使用性能分析工具如perf,vtune定位热点函数优化业务逻辑或引入线程池处理耗时任务。4. 检查日志输出是否过于频繁IO操作可能成为瓶颈。数据发送不完整或延迟非阻塞发送时未正确处理EAGAIN/EWOULDBLOCK。确保send/write返回EAGAIN时数据被存入outputBuffer并关注EPOLLOUT事件。在handleWrite中继续发送缓冲区内数据发完后要取消关注EPOLLOUT避免busy loop。无法接受新连接accept返回EMFILE文件描述符耗尽。1. 使用getrlimit和setrlimit调整进程可打开的最大文件描述符数。2. 实现优雅关闭确保关闭的连接其fd被正确关闭。3. 可以考虑在EMFILE时临时关闭listenfd的读事件并打开一个空闲的“占位fd”待有连接关闭后再重新打开监听。这是一种高级技巧。5.5 性能调优小贴士禁用Nagle算法对于低延迟要求的服务在创建TcpConnection后立即设置TCP_NODELAYsocket选项避免小数据包的发送延迟。设置SO_REUSEADDR在Acceptor的监听socket上设置SO_REUSEADDR允许服务器在TIME_WAIT状态下的端口上快速重启。调整缓冲区大小根据网络环境调整TCP发送和接收缓冲区大小SO_SNDBUF,SO_RCVBUF但要注意内核有上下限限制。使用TimerQueue为EventLoop增加定时器功能。很多网络操作需要超时控制如连接超时、心跳检测。可以实现一个基于时间轮的定时器队列将其集成到EventLoop中每次事件循环时检查并触发到期的定时器回调。日志与监控在关键路径连接建立、断开、收发包添加轻量级日志并暴露关键指标如当前连接数、每秒请求数的接口便于线上监控和诊断。实现一个生产级别的Reactor网络库远比这个玩具示例复杂涉及更多的边界条件处理、错误处理、性能优化和平台兼容性。但这个简单的实现已经清晰地勾勒出了Reactor模式的所有核心概念和运行机制。理解了这个骨架再去阅读像Muduo、libevent这样的成熟库源码就会有一种“原来如此”的通透感。