《Muduo网络库:实现Channel通道以及Poller抽象基类》

实现了日志功能之后,再来封装一下socket地址类型。

实现InetAddress类

InetAddress.h

cpp 复制代码
#pragma once

#include <arpa/inet.h>
#include <netinet/in.h>
#include <string>

// 封装socket地址类型(sockaddr_in结构)
class InetAddress
{
public:
    explicit InetAddress(uint16_t port, std::string ip = "127.0.0.1");
    explicit InetAddress(const struct sockaddr_in &addr)
        : addr_(addr)
    {
    }
    // 获取IP
    std::string toIp() const;
    // 获取IP:端口  
    std::string toIpPort() const;
    // 获取端口
    uint16_t toPort() const;
    // 获取sockaddr_in结构
    const sockaddr_in *getSockAddr() const
    {
        return &addr_;
    }

private:
    sockaddr_in addr_;
};

InetAddress.cc

cpp 复制代码
#include "InetAddress.h"

#include <stdio.h>
#include <string.h>

InetAddress::InetAddress(uint16_t port, std::string ip)
{
    bzero(&addr_, 0);
    addr_.sin_family = AF_INET;
    addr_.sin_port = htons(port);
    addr_.sin_addr.s_addr = inet_addr(ip.c_str());
}

// 获取IP
std::string InetAddress::toIp() const
{
    char buf[64] = {0};
    inet_ntop(AF_INET, &addr_.sin_addr, buf, sizeof buf);
    return buf;
}
// 获取IP:端口
std::string InetAddress::toIpPort() const
{
    // 格式ip:port
    char buf[64] = {0};
    inet_ntop(AF_INET, &addr_.sin_addr, buf, sizeof buf);
    size_t iplen = strlen(buf);
    uint16_t port = ntohs(addr_.sin_port);
    sprintf(buf + iplen, ":%u", port);
    return buf;
}
// 获取端口
uint16_t InetAddress::toPort() const
{
    return ntohs(addr_.sin_port);
}

// 可以测试功能代码是否编写正确
// #include <iostream>
// int main()
// {
//     InetAddress addr(8080);
//     std::cout << addr.toIpPort() << std::endl;
//     return 0;
// }

封装好了socket地址类型,下面就开始着手编写TcpServer类了。

当我们使用过Muduo网络库服务器编程就知道TcpServer相当于是Muduo库给用户提供的编写服务器的入口的一个类,把Muduo库所有的有关服务器编程相关的东西都打包到一起了,包括反应堆、事件分发器、事件回调等。

我们看以下Muduo的源码也可以看到。

TcpServer类就是打包所有资源的一个"盒子",那我们一步一步来剖析。

EventLoop,就是Reactor模型里面最重要的事件循环了,可以想象成epoll。

EventLoop事件循环有两个重要的成员:

  • Poller抽象基类:由EpollPoller(基于epoll)和PollPoller(基于poll)继承,它关注的是poll和epoll感兴趣的事件。
  • Channel通道:它所关注的是fd和它感兴趣的事件以及实际Poller返回的fd上发生的事件。

简单理解,EventLoop就是事件循环,最重要的两个东西,一个是epoll,一个是epoll所监听的fd以及它所感兴趣的事件和最终epoll_wait通知的发生的事件。

下面我们先将Channel和Poller实现,进而来实现EventLoop。

EventLoop.h

cpp 复制代码
#pragma once

// 事件循环类 主要包含了两个大模块 Channel Poller(epoll的抽象)
class EventLoop
{
public:
private:
};

EventLoop.cc

cpp 复制代码
#include "EventLoop.h"

实现Channel通道

sockfd以及感兴趣的事件和发生的事件、事件相应的回调操作都在Channel通道里。

Channel.h

cpp 复制代码
#pragma once

#include "noncopyable.h"
#include "Timestamp.h"

#include <functional>
#include <memory>

class EventLoop;

/**
 * 理清楚 EventLoop、Channel、Poller之间的关系。在Reactor模型上对应多路事件分发器
 * Channel 理解为通道 封装了sockfd和其感兴趣的事件event,比如EPOLLIN、EPOLLOUT
 * 还绑定了Poller返回的具体事件,Poller返回通知Channel发生的事件后,Channel调用相应的事件回调操作
 */

class Channel : noncopyable
{
public:
    using EventCallback = std::function<void()>;              // 事件回调
    using ReadEventCallback = std::function<void(Timestamp)>; // 只读事件回调
    Channel(EventLoop *loop, int fd);
    ~Channel();

    // 得到poller通知的发生的事件以后,处理事件的
    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); }

    // 防止当channel被手动remove掉,channel还在执行回调操作
    void tie(const std::shared_ptr<void>&);

    int fd() const { return fd_; }                       // 获取当前channel的fd_
    int events() const { return events_; }               // 获取当前channel的感兴趣的事件events_
    void set_revents(int revt) { revents_ = revt; }      // used by poller

    // 设置fd相应的事件状态
    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(); }
    
    // 返回channel fd当前的事件状态
    bool isNoneEvent() const { return events_ == kNoneEvent; }
    bool isWriting() const { return events_ & kWriteEvent; }
    bool isReading() const { return events_ & kReadEvent; }

    int index() { return index_; }              // 获取当前channel的状态index_
    void set_index(int idx) { index_ = idx; }   // 设置channel当前的状态 used by poller

    // 返回当前channel属于哪一个EventLoop
    // one loop per thread
    EventLoop* ownerLoop() { return loop_; }

    // 在Poller中移除Channel
    void remove();

private:

    // 在Poller中更新Channel
    void update();

    void handleEventWithGuard(Timestamp receiveTime);

    // channel感兴趣的事件的状态
    static const int kNoneEvent;   // 对任何事件不感兴趣
    static const int kReadEvent;   // 可读事件
    static const int kWriteEvent;  // 可写事件

    EventLoop *loop_; // 定义Channel所属的事件循环EventLoop
    int fd_;          // fd, poller监听的对象
    int events_;      // 注册fd感兴趣的事件
    int revents_;     // poller返回的具体发生的事件
    int index_;       // channel的初始状态
    std::weak_ptr<void> tie_;
    bool tied_;

    // 因为Channel通道能获知fd最终发生的具体事件revents,所以它负责调用具体事件的回调操作,根据事件的类型调用相应事件的回调
    ReadEventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;
};

Channel.cc

cpp 复制代码
#include "Channel.h"
#include "EventLoop.h"
#include "Logger.h"

#include <sys/epoll.h>

const int Channel::kNoneEvent = 0;
const int Channel::kReadEvent = EPOLLIN | EPOLLPRI;
const int Channel::kWriteEvent = EPOLLOUT;

// EventLoop: ChannelList Poller
Channel::Channel(EventLoop *loop, int fd)
    : loop_(loop), fd_(fd), events_(0), revents_(0), index_(-1), tied_(false)
{
}
Channel::~Channel()
{
}

// channel的tie方法什么时候调用过?
void Channel::tie(const std::shared_ptr<void> &obj)
{
    tie_ = obj;
    tied_ = true;
}

/**
 * 当改变channel所表示fd的events事件状态后,update负责在poller里面更改fd相应的事件(也就是epoll_ctl操作)
 * EventLoop:ChannelList、Poller。一个EventLoop有一个poller和多个channel
 * channel和poller是两个不同的模块,channel无法直接操作poller,但是他们都属于一个EventLoop。
 */
// 在channel所属的EventLoop中,更新Poller中当前的channel
void Channel::update()
{
    // 通过channel所属的EventLoop,调用poller的相应方法,更新fd的events事件
    // add code...
    // loop_->updateChannel(this);
}
// 在channel所属的EventLoop中,把Poller中当前的channel删掉
void Channel::remove()
{
    // add code...
    // loop_->removeChannel(this);
}

// fd得到poller通知以后,处理事件的
void Channel::handleEvent(Timestamp receiveTime)
{
    if (tied_)
    {
        std::shared_ptr<void> guard = tie_.lock();
        if (guard)
        {
            handleEventWithGuard(receiveTime);
        }
    }
    else
    {
        handleEventWithGuard(receiveTime);
    }
}

// 根据poller通知channel发生的具体事件,由channel负责调用具体的事件回调操作
void Channel::handleEventWithGuard(Timestamp receiveTime)
{
    LOG_INFO("channel handleEvent revents:%d\n", 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_();
        }
    }
}

也就是说Channel向Poller中注册fd及其感兴趣的事件,Poller未来通过epoll_wait监听到fd上发生的事件后,通知返回给Channel,Channel得到fd发生的事件之后调用我们预先设定好的相应事件的回调操作处理事件。

实现Poller抽象基类

Muduo并没有直接使用epoll或poll,而是使用了一个Poller抽象类,通过引用不同的派生类对象,调用他们的同名覆盖方法,就可以非常方便的去扩展不同的IO复用能力。

Poller.h

cpp 复制代码
#pragma once

#include "noncopyable.h"
#include "Timestamp.h"

#include <vector>
#include <unordered_map>

class Channel;
class EventLoop;

/**
 * muduo库中多路事件分发器的核心IO复用模块
 * 理清楚EventLoop、Channel、Poller三者之间的关系
 */
class Poller : noncopyable
{
public:
    using ChannelList = std::vector<Channel *>;
    Poller(EventLoop *loop);
    virtual ~Poller() = default;

    // 给所有IO复用保留统一的接口
    virtual Timestamp poll(int timeoutMs, ChannelList *activeChannels) = 0;
    virtual void updateChannel(Channel *channel) = 0;
    virtual void removeChannel(Channel *channel) = 0;

    // 判断参数channel是否在当前Poller当中
    bool hasChannel(Channel *channel) const;

    // EventLoop可以通过该接口获取默认的IO复用的具体实现
    static Poller *newDefaultPoller(EventLoop *loop);

protected:
    // unordered_map的key表示sockfd,value表示sockfd所属的channel通道
    using ChannelMap = std::unordered_map<int, Channel *>;
    ChannelMap channels_;

private:
    EventLoop *ownerloop_; // 定义Poller所属的EventLoop事件循环
};

Poller.cc

cpp 复制代码
#include "Poller.h"
#include "Channel.h"

Poller::Poller(EventLoop *loop)
    : ownerloop_(loop)
{
}

bool Poller::hasChannel(Channel *channel) const
{
    auto it = channels_.find(channel->fd());
    return it != channels_.end() && it->second == channel;
}

一、

Poller通过unordered_map定义了一个ChannelMap,保存(以fd映射channel*的方式)了所有要监听的channel通道。当Channel向Poller注册一个channel通道时,Poller就会调用相应的方法将该channel通道保存到ChannelMap中,当Channel要删除一个通道时,Poller也会将其从ChannelMap中删除。

同时定义了一个ChannelList,未来EpollPoller通过epoll_wait检测到某channel有事件发生时,就将该channel添加到ChannelList中,EventLoop就拿到了Poller返回的channel列表了。

二、

Poller是一个抽象类基类,EpollPoller(基于epoll)或PollPoller(基于poll)要继承这个基类,如果在Poller.cc中实现static Poller *newDefaultPoller(EventLoop *loop),这个函数是用来获取一个具体的IO复用方式的,也就是poll或epoll,Poller抽象基类与具体的实现方式耦合性高,基类不应该知道其派生类的存在。

所以获取具体的IO复用方式的实现,在单独的公共的源文件中实现。通过一个公共的文件,分解了Poller与具体的IO复用方式强耦合。

DefaultPoller.cc

cpp 复制代码
#include "Poller.h"
#include <stdlib.h>

Poller *Poller::newDefaultPoller(EventLoop *loop)
{
    if (getenv("MUDUO_USE_POLL"))
    {
        return nullptr; // 生成poll的实例,这里目前还没有实现poll
    }
    else
    {
        return nullptr; // 生成epoll的实例,这里目前还没有实现epoll
    }
}

实现EpollPoller类

Muduo库源码中实现了EpollPoller(基于epoll)和PollPoller(基于poll),我们只实现EpollPoller。EpollPoller继承Poller抽象基类,重写Poller的抽象方法。

要理清EventLoop、Channel、Poller三者之间的关系。

Muduo库采用one loop per thread,一个线程一个事件循环,这是现在高性能服务器设计普遍采用的一种模式。基于充分利用多核CPU性能,线程数肯定不只一个,也就是可以有多个事件循环。一个线程对应一个EventLoop,一个EventLoop对应一个Poller,一个Poller可以监听多个Channel(多路)。

EpollPoller.h

cpp 复制代码
#pragma once

#include "Poller.h"
#include "Timestamp.h"

#include <vector>
#include "sys/epoll.h"

class EventLoop;
class Channel;

/**      
 * epoll的使用
 * epoll_create
 * epoll_ctl
 * epoll_wait
 */
class EpollPoller : public Poller
{
public:
    EpollPoller(EventLoop *loop);
    ~EpollPoller() override;

    // 重写基类Poller的抽象方法
    Timestamp poll(int timeoutMs, ChannelList *activeChannels) override;
    void updateChannel(Channel *channel) override;
    void removeChannel(Channel *channel) override;

private:
    static const int kInitEventListSize = 16;
    // 填写活跃的连接
    void fillActiveChannels(int numEvents, ChannelList *activeChannels) const;
    // 更新channel通道
    void update(int operation, Channel *channel);

    using EventList = std::vector<struct epoll_event>;
    int epollfd_;
    EventList events_;  // 存放epoll_wait返回的发生的事件的数组
};

EpollPoller.cc

cpp 复制代码
#include "EpollPoller.h"
#include "Logger.h"
#include "Channel.h"

#include <errno.h>
#include <unistd.h>
#include <string.h>

// 表示channel通道的状态
const int kNew = -1;    // channel未添加到Poller中  channel的成员index=-1
const int kAdded = 1;   // channel已添加到Poller中
const int kDeleted = 2; // channel从Poller中删除

EpollPoller::EpollPoller(EventLoop *loop)
    : Poller(loop), epollfd_(epoll_create1(EPOLL_CLOEXEC)), events_(kInitEventListSize)
{
    if (epollfd_ < 0)
    {
        LOG_FATAL("epoll_create error:%d\n", errno)
    }
}
EpollPoller::~EpollPoller()
{
    close(epollfd_);
}

Timestamp EpollPoller::poll(int timeoutMs, ChannelList *activeChannels)
{
    // 实际用LOG_DEBUG输出日志更合理
    LOG_INFO("func=%s => fd total count:%d \n", __FUNCTION__, channels_.size());
    int numEvents = epoll_wait(epollfd_, &*events_.begin(), static_cast<int>(events_.size()), timeoutMs);
    int saveErrno = errno;
    Timestamp now(Timestamp::now());
    if (numEvents > 0)
    {
        LOG_INFO("%d events happened \n", numEvents);
        fillActiveChannels(numEvents, activeChannels);
        if (numEvents == events_.size())
        {
            events_.resize(events_.size() * 2);
        }
    }
    else if (numEvents == 0) // 超时
    {
        LOG_DEBUG("%s timeout! \n", __FUNCTION__);
    }
    else // 错误
    {
        if (saveErrno != EINTR)
        {
            errno = saveErrno;
            LOG_ERROR("EPollPoller::poll() err!");
        }
    }
    return now;
}

// 填写活跃的连接
void EpollPoller::fillActiveChannels(int numEvents, ChannelList *activeChannels) const
{
    for (int i = 0; i < numEvents; i++)
    {
        Channel *channel = static_cast<Channel *>(events_[i].data.ptr);
        channel->set_revents(events_[i].events);
        activeChannels->push_back(channel); // EventLoop就拿到了它的Poller给它返回的所有发生事件的channel列表了
    }
}

/**
 * Channel update remove => EventLoop updateChannel removeChannel => Poller updateChannel removeChannel
 *                EventLoop  => 未来通过Poller.poll获取就绪的channel
 *    ChannelList               Poller
 *                         ChannelMap <fd,channel*>
 * 当一个channel注册到Poller中,Poller就将该channel添加ChannelMap中
 * 当一个channel从Poller中删除,Poller就将该channel从ChannelMap中删除
 */
void EpollPoller::updateChannel(Channel *channel)
{
    const int index = channel->index();
    LOG_INFO("func=%s => fd=%d events=%d index=%d \n", __FUNCTION__, channel->fd(), channel->events(), index);
    if (index == kNew | index == kDeleted) // channel没有在Poller上注册过或已从Poller上删除
    {
        if (index == kNew)
        {
            int fd = channel->fd();
            channels_[fd] = channel;
        }
        channel->set_index(kAdded);
        update(EPOLL_CTL_ADD, channel);
    }
    else // channel已经在Poller上注册过
    {
        int fd = channel->fd();
        if (channel->isNoneEvent())
        {
            update(EPOLL_CTL_DEL, channel);
            channel->set_index(kDeleted);
        }
        else
        {
            update(EPOLL_CTL_MOD, channel);
        }
    }
}
// 从Poller中删除channel
void EpollPoller::removeChannel(Channel *channel)
{
    int fd = channel->fd();
    channels_.erase(fd);

    LOG_INFO("func=%s => fd=%d \n", __FUNCTION__, fd);

    int index = channel->index();
    if (index == kAdded)
    {
        update(EPOLL_CTL_DEL, channel);
    }
    channel->set_index(kNew);
}
// 更新channel通道 epoll_ctl add/del/mod
void EpollPoller::update(int operation, Channel *channel)
{
    struct epoll_event event;
    memset(&event, 0, sizeof event);

    int fd = channel->fd();

    event.events = channel->events();
    event.data.fd = fd;
    event.data.ptr = channel;

    if (epoll_ctl(epollfd_, operation, fd, &event) < 0)
    {
        if (operation == EPOLL_CTL_DEL)
        {
            LOG_ERROR("epoll_ctl del error:%d\n", errno);
        }
        else
        {
            LOG_FATAL("epoll_ctl add/mod error:%d\n", errno);
        }
    }
}

将Channel通道和Poller抽象基类以及EpollPoller类实现完成,并理清了其之间的关系,下面我们再来实现EventLoop。

相关推荐
egoist20234 小时前
[linux仓库]深入解析Linux动态链接与动态库加载:理解背后的原理与技巧
linux·服务器·编辑器·动态库·got
moxiaoran57534 小时前
linux普通账号管理
linux·运维·服务器
chilavert3184 小时前
技术演进中的开发沉思-119Linux命令篇:系统设置命令(上)
linux·运维·服务器
半桔4 小时前
【网络编程】深入 HTTP:从报文交互到服务构建,洞悉核心机制
linux·网络·c++·网络协议·http·交互
hqwest5 小时前
QT肝8天07--连接数据库
开发语言·数据库·c++·qt·sqlite·上位机·qt开发
WaWaJie_Ngen5 小时前
LevOJ P2080 炼金铺 II [矩阵解法]
c++·线性代数·算法·矩阵
姝孟5 小时前
笔记(C++篇)—— Day 12(类的默认成员函数)
c++·笔记·学习
tpoog5 小时前
[C++项目组件]Elasticsearch简单介绍
开发语言·c++·elasticsearch
数据皮皮侠6 小时前
中国绿色制造企业数据(绿色工厂|绿色供应链|绿色园区|绿色产品,2017-2023)
大数据·运维·服务器·人工智能·制造·微信开放平台