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

手写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_();}}
}

相关文章:

  • 接口测试中缓存处理策略
  • Suna 开源 AI Agent 安装配置过程全解析(输出与交互详解)
  • 国产具身大模型首入汽车工厂,全场景验证开启工业智能新阶段
  • Vuex 自动化生成工具
  • BeckHoff <--> 基恩士(CV-X300)三维机械手视觉系统的通讯 控制
  • 在GIS 工作流中实现数据处理
  • 百度之星2021——BD202104 萌新
  • 计算机行业光辉开始暗淡
  • 半导体设备基本通信标准介绍
  • HTML的初步学习
  • 生产安全与设备管理如何分清界限?如何正确用设备管理系统?
  • GitLab Web 界面创建分支后pathspec ... did not match any file(s)
  • 【求1+11+111+1111+11…11(n个1)的和除以7的余数是多少】2022-5-5
  • 【机器视觉】单目测距——运动结构恢复
  • 屋顶变身“发电站” ,中天合创屋面分布式光伏发电项目顺利并网!
  • 【Java】在 Spring Boot 中集成 Spring Security + JWT 实现基于 Token 的身份认证
  • 使用分级同态加密防御梯度泄漏
  • 篇章三 论坛系统——环境搭建
  • Unreal从入门到精通之使用 CheatManager 自定义控制台命令
  • c语言中的hashmap
  • 建设网站的命令/免费数据统计网站
  • 淄博网赢网站建设/沈阳网站seo公司
  • 嘉兴网站建设系统/考研培训
  • 做网站开发哪里可以接单/百度贴吧官网app下载
  • 贝壳企业网站管理系统/公司广告推广
  • html网页模板下载html模板免费/佛山网站建设十年乐云seo