在 TCP ⽹络编程中,想要通过 IO 多路复⽤( epoll / poll )监听某个⽂件描述符,就需要把这个 fd 和该 fd 感兴趣 的事件通过 epoll_ctl 注册 到 IO 多路复⽤模块上。当 IO 多路复⽤模块监听到该 fd 发⽣了某个事件。事件监听 器返回发⽣事件的 fd 集合(有哪些 fd 发⽣了事件)以及每个 fd 的事件集合(每个 fd 具体发⽣了什么事件)。
Channel 类则封装了⼀个 fd 和这个 fd 感兴趣事件以及 IO 多路复⽤模块监听到的每个 fd 的事件集合。同时 Channel类还提供了 设置 该 fd 的感兴趣事件,以及将该 fd 及其感兴趣事件 注册 到事件监听器或从事件监听器上移 除,以及保存了该 fd 的每种事件对应的处理函数。
每个 Channel 对象只属于⼀个 EventLoop ,即只属于⼀个 IO 线程。只负责⼀个⽂件描述符( fd )的 IO 时间分 发,但不拥有这个 fd 。 Channel 把不同的 IO 事件分发为不同的回调,回调⽤ C++11 的特性 function 表示。声明周期由拥有它的类负责。
Channel类其实相当于一个文件描述符的保姆!
在TCP网络编程中,想要IO多路复用监听某个文件描述符,就要把这个fd和该fd感兴趣的事件通过epoll_ctl注册到IO多路复用模块(我管它叫事件监听器)上。当事件监听器监听到该fd发生了某个事件。事件监听器返回发生事件的fd集合]以及[每个fd都发生了什么事件。
Channel类则封装了一个 [fd] 和这个 [fd感兴趣事件] 以及事件监听器监听到 [该fd实际发生的事件]。同时Channel类还提供了设置该fd的感兴趣事件,以及将该fd及其感兴趣事件注册到事件监听器或从事件监听器上移除,以及保存了该fd的每种事件对应的处理函数。
Channel类重要的成员变量:
- fd_这个Channel对象照看的文件描述符 int events_代表fd感兴趣的事件类型集合 int
- revents_代表事件监听器实际监听到该fd发生的事件类型集合,当事件监听器监听到一个fd发生了什么事件,通过Channel::set_revents()函数来设置revents值。
- EventLoop* loop:这个fd属于哪个EventLoop对象,这个暂时不解释。
- read_callback_、write_callback_、close_callback_、error_callback_:这些是std::function类型,代表着这个Channel为这个文件描述符保存的各事件类型发生时的处理函数。比如这个fd发生了可读事件,需要执行可读事件处理函数,这时候Channel类都替你保管好了这些可调用函数,真是贴心啊,要用执行的时候直接管保姆要就可以了
class Channel { public: typedef std::function<void()> EventCallBack; Channel(); explicit Channel(int fd); ~Channel(); // IO事件回调函数的调⽤接⼝ 代码随想录知识星球 从 Channel 的类定义中可以看出,每个 Channel 持有⼀个⽂件描述符,正在监听的事件,已经发⽣的事件(由 Poller 返回),以及各个事件(读、写、更新、错误)回调函数的 Function 对象。 // EventLoop中调⽤Loop开始事件循环 会调⽤Poll得到就绪事件 // 然后依次调⽤此函数处理就绪事件 void HandleEvents(); void HandleRead(); // 处理读事件的回调 void HandleWrite(); // 处理写事件的回调 void HandleUpdate(); // 处理更新事件的回调 void HandleError(); // 处理错误事件的回调 int get_fd(); void set_fd(int fd); // 返回weak_ptr所指向的shared_ptr对象 std::shared_ptr<http::HttpConnection> holder(); void set_holder(std::shared_ptr<http::HttpConnection> holder); // 设置回调函数 void set_read_handler(EventCallBack&& read_handler); void set_write_handler(EventCallBack&& write_handler); void set_update_handler(EventCallBack&& update_handler); void set_error_handler(EventCallBack&& error_handler); void set_revents(int revents); int& events(); void set_events(int events); int last_events(); bool update_last_events(); private: int fd_; // Channel的fd int events_; // Channel正在监听的事件(或者说感兴趣的时间) int revents_; // 返回的就绪事件 int last_events_; // 上⼀此事件(主要⽤于记录如果本次事件和上次事件⼀样 就没必要调⽤ epoll_mod) // weak_ptr是⼀个观测者(不会增加或减少引⽤计数),同时也没有重载->,和*等运算符 所以不能直接使⽤ // 可以通过lock函数得到它的shared_ptr(对象没销毁就返回,销毁了就返回空shared_ptr) // expired函数判断当前对象是否销毁了 std::weak_ptr<http::HttpConnection> holder_; EventCallBack read_handler_; EventCallBack write_handler_; EventCallBack update_handler_; EventCallBack error_handler_; };
从 Channel 的类定义中可以看出,每个 Channel 持有⼀个⽂件描述符,正在监听的事件,已经发⽣的事件(由Poller 返回),以及各个事件(读、写、更新、错误)回调函数的 Function 对象。
总的来说, Channel 就是对 fd 事件的封装,包括注册它的事件以及回调。 EventLoop 通过调⽤
Channel::handleEvent() 来执⾏ Channel 的读写事件。 Channel::handleEvent() 的实现也⾮常简单,就 是⽐较已经发⽣的事件(由 Poller 返回),来调⽤对应的回调函数(读、写、错误)。
// IO事件的回调函数 EventLoop中调⽤Loop开始事件循环 会调⽤Poll得到就绪事件 // 然后依次调⽤此函数处理就绪事件 void Channel::HandleEvents() { events_ = 0; // 触发挂起事件 并且没触发可读事件 if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)) { events_ = 0; return; } // 触发错误事件 if (revents_ & EPOLLERR) { HandleError(); events_ = 0; return; } // 触发可读事件 | ⾼优先级可读 | 对端(客户端)关闭连接 if (revents_ & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) { HandleRead(); } // 触发可写事件 if (revents_ & EPOLLOUT) { HandleWrite(); } //处理更新监听事件(EpollMod) HandleUpdate(); }