C++仿muduo库高并发服务器项目:Channel模块

前言

本篇文章所讲的是本人的个人项目仿muduo库高并发服务器中的Channel模块实现部分。

Channel模块介绍:

  • 功能:Channel模块是对⼀个描述符需要进⾏的IO事件管理的模块,实现对描述符可读,可写,错误...事件的

    管理操作,以及Poller模块对描述符进⾏IO事件监控就绪后,根据不同的事件,回调不同的处理函数功

    能。

  • 意义:让描述符的监控事件在用户态更易维护,触发事件后的操作流程更清晰

  • 功能设计:

    1. 对监控事件的管理:
      1. 描述符是否可读
      2. 描述符是否可写
      3. 对描述符监控可读
      4. 对描述符监控可写
      5. 解除可读事件监控
      6. 解除可写事件监控
      7. 解除所有事件监控
    2. 对监控事件触发后的处理:
      1. 设置对于不同事件的回调处理函数,明确触发某个事件后该如何处理。

线程安全:

Channel为什么要保证线程安全? 不保证线程安全有什么后果?

简单来说,Channel 必须保证线程安全,但其目标并非让自身成为一个能被任意线程随意调用的"线程安全类",而是通过严格的规则,确保所有针对它的操作都在其所属的 IO 线程中执行,从而避免并发问题。

不保证线程安全的后果:

  • 数据竞争:多个线程同时修改 _events(监控事件)或 _revents(就绪事件),导致状态错乱,进而引发 Poller 监控错误的事件或漏掉事件。
  • 生命周期管理混乱:线程 A 正在处理 Channel 的事件回调,线程 B 却将 Channel 及其关联的连接对象销毁,导致线程 A 访问了已释放的内存,程序崩溃。
  • 事件处理乱序:线程池中的工作线程处理完业务逻辑后,想通过 send()发送数据。如果直接在其他线程调用,可能破坏 TCP 连接的字节流顺序,或与 IO 线程的读写事件产生竞争。

注意:这个模块应结合EventLoop模块学习,Channel中的EventLoop指针主要是为了保证线程安全 ,这也是muduo库的精髓之一,它并非采用复杂的锁机制来保证线程安全,那样会严重损害性能,而是采用了一种更为精巧的架构设计。其精髓可以概括为:通过"线程绑定"和"任务派发"机制,将可能引发竞态条件的操作约束在特定的单一线程内执行。

其实就是每个EventLoop都绑定了一个线程,内部含有一个线程ID,在开始时,都会调用assertInLoopThread()来检查当前线程的ID是否与threadId_一致。如果不一致,程序会直接报错终止。这就强制保证了诸如channel->EnableRead()这样的调用,最终必须在Channel所属的IO线程中执行。

想进一步了解,可以看看我写的关于EventLoop模块的博客。

代码实现

c++ 复制代码
class EventLoop;
class Channel
{
private:
    int _fd;
    EventLoop* _loop;
    uint32_t _events;  //当前需要监控的事件
    uint32_t _revents; //当前连接触发的事件
    using EventCallback = std::function<void()>;
    EventCallback _read_callback;   //可读事件被触发的回调函数
    EventCallback _write_callback;  //可写事件被触发的回调函数
    EventCallback _error_callback;  //错误事件被触发的回调函数
    EventCallback _close_callback;  //连接断开事件被触发的回调函数
    EventCallback _event_callback;  //任意事件被触发的回调函数

public:
    Channel(EventLoop* loop,int fd)
        :_fd(fd)
        ,_events(0)
        ,_revents(0)
        ,_loop(loop)
    {}
    int Fd()
    {
        return _fd;
    }
    //获取想要监控的事件
    uint32_t Events()
    {
        return _events;
    }
    //设置实际就绪事件
    void SetEvents(uint32_t events)
    {
        _revents = events;
    }
    void SetReadCallback(const EventCallback& cb)
    {
        _read_callback = cb;
    }
    void SetWriteCallback(const EventCallback& cb)
    {
        _write_callback = cb;
    }
    void SetErrorCallback(const EventCallback& cb)
    {
        _error_callback = cb;
    }
    void SetCloseCallback(const EventCallback& cb)
    {
        _close_callback = cb;
    }
    void SetEventCallback(const EventCallback& cb)
    {
        _event_callback = cb;
    }
    //当前是否监控了可读
    bool ReadAble()
    {
        return (_events & EPOLLIN);
    }        
    //当前是否可写
    bool WriteAble()
    {
        return (_events & EPOLLOUT);
    }    
    //启动读事件监控   
    void EnableRead()
    {
        _events |= EPOLLIN;
        Update();
    }  
    //启动写事件监控
    void EnableWrite()
    {
        _events |= EPOLLOUT;
        Update();
    }  
    //关闭读事件监控
    void DisableRead()
    {
        _events &= ~EPOLLIN;
        Update();
    }     
    //关闭写事件监控
    void DisableWrite()
    {
        _events &= ~EPOLLOUT;
        Update();
    }
    //关闭所有事件
    void DisableAll()
    {
        _events = 0;
        Update();
    }
    //移除监控
    void Remove();
    void Update();
    //事件处理,一旦触发事件调用它,用它来判断自己触发了什么事件,该去哪处理       
    void HandleEvent()
    {
        // 1. 优先处理错误事件:连接已致命,无需也无权再操作数据
        if (_revents & EPOLLERR) {
            if (_error_callback) _error_callback();
            // 错误通常是致命的,处理后直接返回,不再触发其他回调
            if (_event_callback) _event_callback(); // 错误事件也触发通用回调
            return;
        }

        // 2. 处理挂起事件:连接已完全断开且缓冲区数据已经读完
        if ((_revents & EPOLLHUP) && !(_revents & EPOLLIN)) {
            if (_close_callback) _close_callback();
            if (_event_callback) _event_callback(); // 关闭事件也触发通用回调
            return;
        }

        // 3. 处理读事件(包括普通数据、对端半关闭后的剩余数据)
        if (_revents & (EPOLLIN | EPOLLPRI | EPOLLRDHUP)) {
            if (_read_callback) _read_callback();
        }

        // 4. 处理写事件
        if (_revents & EPOLLOUT) {
            if (_write_callback) _write_callback();
        }

        // 5. 最后处理通用事件:任何事件触发(只要执行到这里)都会调用
        // 注意:由于错误和挂起事件已在上方返回,通用事件在那里被提前触发。
        // 而对于读/写事件,它在这里被触发。
        if (_event_callback) _event_callback();
    }    
};

void Channel::Remove()
{
    _loop->RemoveEvent(this);
}

void Channel::Update()
{
    _loop->UpdateEvent(this);
}
相关推荐
Java_小白呀3 小时前
第十四届蓝桥杯大赛软件赛国赛Java大学C组(部分)
职场和发展·蓝桥杯·1024程序员节
YiHanXii3 小时前
this 输出题
前端·javascript·1024程序员节
csdn_aspnet3 小时前
如何在 Ubuntu 24.04/22.04/20.04 上安装 MySQL 8.0
linux·mysql·ubuntu
Dream it possible!3 小时前
LeetCode 面试经典 150_链表_合并两个有序链表(58_21_C++_简单)
leetcode·链表·面试·1024程序员节
Yyyy4823 小时前
Ubuntu22.04LTS基于cephadm快速部署Ceph Reef(18.2.X)集群
linux·服务器·ceph
yoke菜籽3 小时前
面试150——动态规划
1024程序员节
Dontla3 小时前
Tailwind CSS Next.js实战(官方)Tailwind Demo、Tailwind教程
1024程序员节
程琬清君3 小时前
vue3 confirm倒计时
前端·1024程序员节
麦麦大数据3 小时前
F033 vue+neo4j图书智能问答+知识图谱推荐系统 |知识图谱+neo4j+vue+flask+mysql实现代码
vue.js·flask·nlp·neo4j·智能问答·图书·1024程序员节