当前位置: 首页 > backend >正文

手写muduo网络库(二):文件描述符fd及其事件的封装(Channel类的实现)

一、引言

在网络编程中,事件驱动是一种常见的编程模型。Muduo 网络库作为一个高效的 C++ 网络库,采用了事件驱动的设计思想。其中,Channel类在整个架构中扮演着非常重要的角色,它负责封装文件描述符(fd)和与之关联的事件,并处理这些事件的回调。本文将详细介绍 Muduo 网络库中的Channel类。

二、Channel类的作用

Channel类是 Muduo 网络库中一个核心的组件,它的主要作用是将一个文件描述符(如 socket fd)和该文件描述符上感兴趣的事件(如读事件、写事件等)关联起来,在事件发生时上层通过set_revents方法设置发生事件类型并调用相应的回调函数。简单来说,Channel类就像是一个桥梁,连接了底层的 I/O 多路复用机制(如epollpoll等)和上层的业务逻辑。(在后续完成poller和eventloop后会分析三者之间调用逻辑)

三、Channel类的代码结构与关键成员

3.1 头文件包含与前置声明

#pragma once#include "NonCopyable.h"
#include "Timestamp.h"#include <functional>
#include <memory>class EventLoop;

这里包含了必要的头文件,如NonCopyable.hTimestamp.h,并使用了std::functionstd::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)>;

这里定义了两种回调函数类型:EventCallbackReadEventCallbackEventCallback是一个无参数的回调函数,而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 中移除该 ChannelEventLoop 会调用 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_ChannelPoller中的索引。
  • tie_tied_:用于绑定对象,防止在处理事件时对象被提前销毁。
  • readCallback_writeCallback_closeCallback_errorCallback_:分别是读事件、写事件、关闭事件和错误事件的回调函数。

四、总结

4.1 类内参数总结

  • loop_:指向所属的EventLoop对象,用于事件循环,确保Channel对象能在正确的事件循环中工作。
  • fd_:文件描述符,是Poller监听的对象,代表了一个具体的 I/O 资源。
  • events_:注册的文件描述符感兴趣的事件,通过enableReading()enableWriting()等函数进行设置。
  • revents_Poller返回的具体发生的事件,由Poller在检测到事件后设置。
  • index_ChannelPoller中的索引,用于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_();}}
}
http://www.xdnf.cn/news/13324.html

相关文章:

  • 第十章、无线通信之红外遥控协议NEC及红外接收模块驱动的构建
  • 洛谷P1923 【深基9.例4】求第 k 小的数
  • 【SQL学习笔记2】深入理解 CASE WHEN 的魔法用法
  • 可视化在车间质量管控中的创新应用,提升品质
  • python模拟键盘 鼠标操作 通过ctypes调用Windows API实现底层输入模拟
  • filebeat原理架构
  • css~word-break属性
  • 核方法、核技巧、核函数、核矩阵
  • 模型训练-关于token【低概率token, 高熵token】
  • 【Python】 -- 趣味代码 - 飞船大战游戏
  • DiffBP: generative diffusion of 3D molecules for target protein binding
  • 智慧园区综合运营管理平台(SmartPark)和安全EHS平台的分工与协作
  • 电动汽车VCU扭矩控制模式分类方法
  • 【javascript】泡泡龙游戏中反弹和查找匹配算法
  • Jaeger开源分布式追踪平台深度剖析(三)Jaeger默认存储Badger原理剖析
  • 网格布局示例代码解析
  • (三)总结(缓存/ETag请求头)
  • CentOS7下的Redis部署
  • XS2105M IEEE 802.3af 兼容、受电设备接口控制器
  • Day27 函数专题2:装饰器
  • 从中科大镜像获取linux内核5.10.168的git方法
  • Python 字符串、字节串与编解码:数据转换的奥秘
  • 【Redis/1-前置知识】分布式系统概论:架构、数据库与微服务
  • 【力扣数据库知识手册笔记】索引
  • java--怎么定义枚举类
  • 状态模式:对象行为的优雅状态管理之道
  • 图像直方图分析:全面掌握OpenCV与Matplotlib绘制技巧
  • 《通信之道——从微积分到 5G》读书总结
  • 最短回文串解题思路分享
  • 基于大模型预测的输尿管上段积水诊疗方案研究报告