手写muduo网络库(二):文件描述符fd及其事件的封装(Channel类的实现)
一、引言
在网络编程中,事件驱动是一种常见的编程模型。Muduo 网络库作为一个高效的 C++ 网络库,采用了事件驱动的设计思想。其中,Channel
类在整个架构中扮演着非常重要的角色,它负责封装文件描述符(fd)和与之关联的事件,并处理这些事件的回调。本文将详细介绍 Muduo 网络库中的Channel
类。
二、Channel
类的作用
Channel
类是 Muduo 网络库中一个核心的组件,它的主要作用是将一个文件描述符(如 socket fd)和该文件描述符上感兴趣的事件(如读事件、写事件等)关联起来,在事件发生时上层通过set_revents方法设置发生事件类型并调用相应的回调函数。简单来说,Channel
类就像是一个桥梁,连接了底层的 I/O 多路复用机制(如epoll
、poll
等)和上层的业务逻辑。(在后续完成poller和eventloop后会分析三者之间调用逻辑)
三、Channel
类的代码结构与关键成员
3.1 头文件包含与前置声明
#pragma once#include "NonCopyable.h"
#include "Timestamp.h"#include <functional>
#include <memory>class EventLoop;
这里包含了必要的头文件,如NonCopyable.h
和Timestamp.h
,并使用了std::function
和std::memory
。同时,对EventLoop
类进行了前置声明,由于在Channel的头文件中只使用了EventLoop
的指针,不访问其具体实现所以不需要引用其头文件,避免了头文件的循环包含。
3.2 类的定义与继承
class Channel: NonCopyable
{
public:// ...
private:// ...
};
Channel
类继承自NonCopyable
,这意味着该类的对象不能被复制,防止了不必要的对象复制操作,提高了性能。
NonCopyable定义:
class NonCopyable { protected:NonCopyable(){}~NonCopyable(){}NonCopyable(const NonCopyable&) = delete;NonCopyable& operator=(const NonCopyable&) = delete; };
通过删除类的拷贝构造,赋值运算符实现类及其派生类无法被复制。
3.3 回调函数类型定义
using EventCallback = std::function<void()>;
using ReadEventCallback = std::function<void(Timestamp)>;
这里定义了两种回调函数类型:EventCallback
和ReadEventCallback
。EventCallback
是一个无参数的回调函数,而ReadEventCallback
是一个带有Timestamp
参数的回调函数,用于处理读事件。
3.4 构造函数与析构函数
Channel(EventLoop *loop, int fd);
~Channel() = default;
构造函数接受一个EventLoop
指针(channel所属的loop)和一个文件描述符(channel管理的文件描述符)作为参数,用于初始化Channel
对象。析构函数使用了default
关键字,采用默认的析构行为(因为类的成员中只有loop_是裸指针,但其生命周期显然长于channel,析构时没有成员需要手动清理)。
Channel::Channel(EventLoop *loop, int fd): loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{
}
3.5 事件处理函数
void handleEvent(Timestamp receiveTime);
该函数用于处理文件描述符上发生的事件,根据revents_
的值调用相应的回调函数。
void Channel::handleEvent(Timestamp receiveTime)
{if (tied_){std::shared_ptr<void> guard = tie_.lock();if (guard){handleEventWithGuard(receiveTime);}// 如果提升失败了 就不做任何处理 说明Channel的TcpConnection对象已经不存在了}else{handleEventWithGuard(receiveTime);}
}
handleEvent
方法用于处理文件描述符上发生的事件。如果 tied_
为 true
,则先尝试提升 tie_
所指向的 std::shared_ptr
对象,如果提升成功则调用 handleEventWithGuard
方法处理事件;如果提升失败,则不做任何处理。如果 tied_
为 false
,则直接调用 handleEventWithGuard
方法处理事件。
void Channel::handleEventWithGuard(Timestamp receiveTime)
{LOG_INFO << "channel handleEvent revents: " << revents_;// 关闭if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)){if (closeCallback_){closeCallback_();}}// 错误if (revents_ & EPOLLERR){if (errorCallback_){errorCallback_();}}// 读if (revents_ & (EPOLLIN | EPOLLPRI)){if (readCallback_){readCallback_(receiveTime);}}// 写if (revents_ & EPOLLOUT){if (writeCallback_){writeCallback_();}}
}
handleEventWithGuard
方法根据内核返回的实际发生的事件(revents_
)调用相应的回调函数。
- 关闭事件:当
revents_
包含EPOLLHUP
且不包含EPOLLIN
时,表示文件描述符被关闭,调用closeCallback_
回调函数。 - 错误事件:当
revents_
包含EPOLLERR
时,表示文件描述符发生错误,调用errorCallback_
回调函数。 - 读事件:当
revents_
包含EPOLLIN
或EPOLLPRI
时,表示文件描述符有数据可读,调用readCallback_
回调函数。 - 写事件:当
revents_
包含EPOLLOUT
时,表示文件描述符可以写数据,调用writeCallback_
回调函数。
3.6 回调函数设置
void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); }
void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }
void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }
void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }
这些函数用于设置不同类型事件的回调函数,分别是读事件、写事件、关闭事件和错误事件的回调函数。(由于设置回调的调用并不频繁,这里没有写成接收左右值引用传递的方法,但写了更好)
3.7 事件状态操作
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(); }
这些函数用于启用或禁用文件描述符上的读事件、写事件,以及禁用所有事件。每次修改事件状态后,都会调用update()
函数更新事件。
void Channel::update()
{// 通过channel所属的eventloop,调用poller的相应方法,注册fd的events事件loop_->updateChannel(this);
}
更新事件需要epoll_ctl函数执行,但其具体实现被封装到poller类中,那么需要通过channel和poller的管理者eventloop作为桥梁,所以update
方法借助其所属的 EventLoop
来调用 Poller
的相应方法来注册或更新该文件描述符上关注的事件。
3.8 其他成员函数
void tie(const std::shared_ptr<void> &);
int fd() const { return fd_; }
int events() const { return events_; }
void set_revents(int revt) { revents_ = revt; }
bool isNoneEvent() const { return events_ == kNoneEvent; }
bool isWriting() const { return events_ & kWriteEvent; }
bool isReading() const { return events_ & kReadEvent; }
int index() { return index_; }
void set_index(int idx) { index_ = idx; }
EventLoop *ownerLoop() { return loop_; }
void remove();
这些函数提供了获取文件描述符、事件状态、索引等信息的接口,以及绑定对象、移除Channel
等操作。
void Channel::remove()
{loop_->removeChannel(this);
}
remove
方法用于从 EventLoop
中移除该 Channel
,EventLoop
会调用 Poller
的相应方法来移除该文件描述符上的事件注册
3.9 私有成员变量
private:void update();void handleEventWithGuard(Timestamp receiveTime);//这两个私有方法实现见上文static const int kNoneEvent;static const int kReadEvent;static const int kWriteEvent;EventLoop *loop_; // 事件循环const int fd_; // fd,Poller监听的对象int events_; // 注册fd感兴趣的事件int revents_; // Poller返回的具体发生的事件int index_;std::weak_ptr<void> tie_;bool tied_;ReadEventCallback readCallback_;EventCallback writeCallback_;EventCallback closeCallback_;EventCallback errorCallback_;/*
const int Channel::kNoneEvent = 0; //空事件
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI; //读事件
const int Channel::kWriteEvent = EPOLLOUT; //写事件
*/
loop_
:指向所属的EventLoop
对象,用于事件循环。fd_
:文件描述符,是Poller
监听的对象。events_
:注册的文件描述符感兴趣的事件。revents_
:Poller
返回的具体发生的事件。index_
:Channel
在Poller
中的索引。tie_
和tied_
:用于绑定对象,防止在处理事件时对象被提前销毁。readCallback_
、writeCallback_
、closeCallback_
和errorCallback_
:分别是读事件、写事件、关闭事件和错误事件的回调函数。
四、总结
4.1 类内参数总结
loop_
:指向所属的EventLoop
对象,用于事件循环,确保Channel
对象能在正确的事件循环中工作。fd_
:文件描述符,是Poller
监听的对象,代表了一个具体的 I/O 资源。events_
:注册的文件描述符感兴趣的事件,通过enableReading()
、enableWriting()
等函数进行设置。revents_
:Poller
返回的具体发生的事件,由Poller
在检测到事件后设置。index_
:Channel
在Poller
中的索引,用于Poller
管理Channel
对象。tie_
和tied_
:用于绑定对象,防止在处理事件时对象被提前销毁,提高了程序的安全性。
4.2 回调函数总结
readCallback_
:读事件回调函数,由上层传递,用于处理文件描述符上的读事件。writeCallback_
:写事件回调函数,由上层传递,用于处理文件描述符上的写事件。closeCallback_
:关闭事件回调函数,由上层传递,用于处理文件描述符关闭的情况。errorCallback_
:错误事件回调函数,由上层传递,用于处理文件描述符上发生的错误事件。
通过Channel
类,Muduo 网络库将底层的 I/O 多路复用机制和上层的业务逻辑进行了有效的分离,提高了代码的可维护性和可扩展性。同时,通过回调函数的方式,使得上层业务逻辑可以方便地处理不同类型的事件,增强了代码的灵活性。
附录:
#pragma once#include "NonCopyable.h"
#include "Timestamp.h"#include <functional>
#include <memory>class EventLoop;class Channel: NonCopyable
{
public:using EventCallback = std::function<void()>;using ReadEventCallback = std::function<void(Timestamp)>;Channel(EventLoop *loop, int fd);~Channel() = default;void handleEvent(Timestamp receiveTime);void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); }void setWriteCallback(EventCallback cb) { writeCallback_ = std::move(cb); }void setCloseCallback(EventCallback cb) { closeCallback_ = std::move(cb); }void setErrorCallback(EventCallback cb) { errorCallback_ = std::move(cb); }void tie(const std::shared_ptr<void> &);int fd() const { return fd_; }int events() const { return events_; }void set_revents(int revt) { revents_ = revt; }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(); }bool isNoneEvent() const { return events_ == kNoneEvent; }bool isWriting() const { return events_ & kWriteEvent; }bool isReading() const { return events_ & kReadEvent; }int index() { return index_; }void set_index(int idx) { index_ = idx; }EventLoop *ownerLoop() { return loop_; }void remove();private:void update();void handleEventWithGuard(Timestamp receiveTime);static const int kNoneEvent;static const int kReadEvent;static const int kWriteEvent;EventLoop *loop_; // 事件循环const int fd_; // fd,Poller监听的对象int events_; // 注册fd感兴趣的事件int revents_; // Poller返回的具体发生的事件int index_;std::weak_ptr<void> tie_;bool tied_;ReadEventCallback readCallback_;EventCallback writeCallback_;EventCallback closeCallback_;EventCallback errorCallback_;
};
#include "LogStream.h"
#include "Channel.h"
#include "EventLoop.h"#include <sys/epoll.h>const int Channel::kNoneEvent = 0; //空事件
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI; //读事件
const int Channel::kWriteEvent = EPOLLOUT; //写事件Channel::Channel(EventLoop *loop, int fd): loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{
}void Channel::tie(const std::shared_ptr<void> &obj)
{tie_ = obj;tied_ = true;
}void Channel::update()
{// 通过channel所属的eventloop,调用poller的相应方法,注册fd的events事件loop_->updateChannel(this);
}void Channel::remove()
{loop_->removeChannel(this);
}void Channel::handleEvent(Timestamp receiveTime)
{if (tied_){std::shared_ptr<void> guard = tie_.lock();if (guard){handleEventWithGuard(receiveTime);}// 如果提升失败了 就不做任何处理 说明Channel的TcpConnection对象已经不存在了}else{handleEventWithGuard(receiveTime);}
}void Channel::handleEventWithGuard(Timestamp receiveTime)
{LOG_INFO << "channel handleEvent revents: " << revents_;// 关闭if ((revents_ & EPOLLHUP) && !(revents_ & EPOLLIN)){if (closeCallback_){closeCallback_();}}// 错误if (revents_ & EPOLLERR){if (errorCallback_){errorCallback_();}}// 读if (revents_ & (EPOLLIN | EPOLLPRI)){if (readCallback_){readCallback_(receiveTime);}}// 写if (revents_ & EPOLLOUT){if (writeCallback_){writeCallback_();}}
}