最核⼼的部分就是 EventLoop 、 Channel 以及 Poller 三个类,其中 EventLoop 可以看作是对业务线程的封装,⽽ Channel 可以看作是对每个已经建⽴连接的封装(即 accept(3) 返回的⽂件描述符)
EventLoop
class EventLoop { public: typedef std::function<void()> Function; // 初始化poller, event_fd,给 event_fd 注册到 epoll 中并注册其事件处理回调 EventLoop(); ~EventLoop(); // 开始事件循环 调⽤该函数的线程必须是该 EventLoop 所在线程,也就是 Loop 函数不能跨线程调⽤ void Loop(); // 停⽌ Loop void StopLoop(); // 如果当前线程就是创建此EventLoop的线程 就调⽤callback(关闭连接 EpollDel) 否则就放⼊等待执⾏ 函数区 void RunInLoop(Function&& func); // 把此函数放⼊等待执⾏函数区 如果当前是跨线程 或者正在调⽤等待的函数则唤醒 void QueueInLoop(Function&& func); // 把fd和绑定的事件注册到epoll内核事件表 void PollerAdd(std::shared_ptr<Channel> channel, int timeout = 0); // 在epoll内核事件表修改fd所绑定的事件 void PollerMod(std::shared_ptr<Channel> channel, int timeout = 0); // 从epoll内核事件表中删除fd及其绑定的事件 void PollerDel(std::shared_ptr<Channel> channel); // 只关闭连接(此时还可以把缓冲区数据写完再关闭) void ShutDown(std::shared_ptr<Channel> channel); 代码随想录知识星球 从 EventLoop 的类定义中可以看出,除了⼀些状态量以外,每个 EventLoop 持有⼀个 Poller 的智能指针(对 epoll / poll 的封装),⼀个⽤于 EventLoop 之间通信的 Channel ,⾃⼰的线程 id,互斥锁以及装有等待处理函 数的 vector 。很明显, EventLoop 并不直接管理各个连接的 Channel (⽂件描述符的封装),⽽是通过 Poller 来进⾏的。 EventLoop 中最核⼼的函数就是 EventLoop::Loop() 。 bool is_in_loop_thread(); private: // 创建eventfd 类似管道的 进程间通信⽅式 static int CreateEventfd(); void HandleRead(); // eventfd的读回调函数(因为event_fd写了数据,所以触发 可读事件,从event_fd读数据) void HandleUpdate(); // eventfd的更新事件回调函数(更新监听事件) void WakeUp(); // 异步唤醒SubLoop的epoll_wait(向event_fd中写⼊数据) void PerformPendingFunctions(); // 执⾏正在等待的函数(SubLoop注册EpollAdd连接套接字以 及绑定事件的函数) private: std::shared_ptr<Poller> poller_; // io多路复⽤ 分发器 int event_fd_; // ⽤于异步唤醒 SubLoop 的 Loop 函数中的 Poll(epoll_wait因为还没有注册fd会⼀直阻塞) std::shared_ptr<Channel> wakeup_channel_; // ⽤于异步唤醒的 channel pid_t thread_id_; // 线程id mutable locker::MutexLock mutex_; std::vector<Function> pending_functions_; // 正在等待处理的函数 bool is_stop_; // 是否停⽌事件循环 bool is_looping_; // 是否正在事件循环 bool is_event_handling_; // 是否正在处理事件 bool is_calling_pending_functions_; // 是否正在调⽤等待处理的函数 };
从 EventLoop 的类定义中可以看出,除了⼀些状态量以外,每个 EventLoop 持有⼀个 Poller 的智能指针(对epoll / poll 的封装),⼀个⽤于 EventLoop 之间通信的 Channel ,⾃⼰的线程 id ,互斥锁以及装有等待处理函数的 vector 。很明显, EventLoop 并不直接管理各个连接的 Channel (⽂件描述符的封装),⽽是通过 Poller 来进⾏的。 EventLoop 中最核⼼的函数就是 EventLoop::Loop() 。
void EventLoop::Loop() { // 开始事件循环 调⽤该函数的线程必须是该EventLoop所在线程 assert(!is_looping_); assert(is_in_loop_thread()); is_looping_ = true; is_stop_ = false; while (!is_stop_) { // 1、epoll_wait阻塞 等待就绪事件 auto ready_channels = poller_->Poll(); is_event_handling_ = true; // 2、处理每个就绪事件(不同channel绑定了不同的callback) for (auto& channel : ready_channels) { channel->HandleEvents(); } is_event_handling_ = false; // 3、执⾏正在等待的函数(fd注册到epoll内核事件表) PerformPendingFunctions(); // 4、处理超时事件 到期了就从定时器⼩根堆中删除(定时器析构会EpollDel掉fd) poller_->HandleExpire(); } is_looping_ = false; }
每个 EventLoop 对象都唯⼀绑定了⼀个线程,这个线程其实就在⼀直执⾏这个函数⾥⾯的 while 循环,这个 while 循环的⼤致逻辑⽐较简单。就是调⽤ Poller::poll() 函数获取事件监听器上的监听结果。接下来在 Loop ⾥⾯就会调⽤监听结果中每⼀个 Channel 的处理函数 HandlerEvent() 。每⼀个 Channel 的处理函数会 根据 Channel 中封装的实际发⽣的事件,执⾏ Channel 中封装的各事件处理函数。(⽐如⼀个 Channel 发⽣ 了可读事件,可写事件,则这个 Channel 的 HandlerEvent() 就会调⽤提前注册在这个 Channel 的可读事件 和可写事件处理函数,⼜⽐如另⼀个 Channel 只发⽣了可读事件,那么 HandlerEvent() 就只会调⽤提前注册在这个 Channel 中的可读事件处理函数。
从中可以看到,每个 EventLoop 实际上就做了四件事
1. epoll_wait阻塞 等待就绪事件(没有注册其他fd时,可以通过event_fd来异步唤醒)
2. 处理每个就绪事件
3. 执⾏正在等待的函数(fd注册到epoll内核事件表)
4. 处理超时事件,到期了就从定时器⼩根堆中删除