[岩禾溪] C++20项目 muduo网络库 项目实战 (2)InetAddress & Channel

发布时间 2023-11-25 10:08:20作者: 岩禾溪

 目录

 ​本项目由 岩禾溪 原创

InetAddress.ixx

模块介绍

类 InetAddress:

C++20 新特性内容:

InetAddress.cpp

函数实现解释:

Channel.ixx

模块介绍

类 Channel:

Channel.cpp

模块导入和常量定义:

类 Channel 的函数实现:

关于注释部分:

更新Logger

Logger.ixx(更新)

 编辑本项目由 岩禾溪 原创

 

项目实战+新特性用法介绍
开源代码+博客解析+视频讲解

 

GitHub+CSDN+BiliBili同步更新,三个平台同名【岩禾溪】

 

GitHub代码链接  GitHub - YanHeXi/muduo_cpp20

项目讲解视频连接:岩禾溪的个人空间-岩禾溪个人主页-哔哩哔哩视频

你的关注是我更新的最大动力

InetAddress.ixx

export module InetAddress;
export import <string>;
export import <arpa/inet.h>;
import <netinet/in.h>;

export class InetAddress
{
public:
    explicit InetAddress(uint16_t port = 0,
                         std::string_view ip = "127.0.0.1");

    explicit InetAddress(const sockaddr_in &addr)
        : addr_(addr) {}

    std::string toIp() const;
    std::string toIpPort() const;

    uint16_t toPort() const { return ntohs(addr_.sin_port); }

    const sockaddr_in *getSockAddr() const { return &addr_; }

    void setSockAddr(const sockaddr_in &addr) { addr_ = addr; }

private:
    sockaddr_in addr_;
};

模块介绍

该模块名为 InetAddress,用于处理网络地址的表示和操作。它依赖于 C++20 中的模块特性进行导出和导入相关头文件,并提供网络地址的基本功能。

InetAddress

  • 构造函数

    • explicit InetAddress(uint16_t port = 0, std::string_view ip = "127.0.0.1");:这是一个构造函数,用于创建一个 InetAddress 类的实例。它接受一个端口号和一个 IP 地址字符串,默认情况下端口号为0,IP地址为 "127.0.0.1"。
  • 成员函数

    • std::string toIp() const;:返回当前 InetAddress 实例的 IP 地址字符串表示。
    • std::string toIpPort() const;:返回当前 InetAddress 实例的 IP 地址和端口号的组合字符串表示。
    • uint16_t toPort() const;:返回当前 InetAddress 实例的端口号。
    • const sockaddr_in *getSockAddr() const;:返回指向当前 InetAddress 实例中 sockaddr_in 结构的指针。
    • void setSockAddr(const sockaddr_in &addr);:设置当前 InetAddress 实例中的 sockaddr_in 结构。

C++20 新特性内容:

  1. Modules (模块)export module InetAddress;export/import 关键字用于导出和导入模块,将 InetAddress 类的定义和相关的头文件进行模块化管理。
  2. std::string_viewstd::string_view 是 C++17 引入的,但在 C++20 中得到了增强。它用于在不拷贝字符串的情况下表示和操作字符串。
  3. Uniform Initialization (统一初始化):在构造函数中,使用了统一初始化方式,允许在一个构造函数中同时提供多个默认参数值。

InetAddress.cpp

import InetAddress;

import <cstring>;
import <netinet/in.h>;

InetAddress::InetAddress(uint16_t port, std::string_view ip)
{
    addr_.sin_family = AF_INET;
    addr_.sin_port = htons(port);
    inet_pton(AF_INET, ip.data(), &addr_.sin_addr);
}

std::string InetAddress::toIp() const
{
    char buf[INET_ADDRSTRLEN];
    inet_ntop(AF_INET, &addr_.sin_addr, buf, INET_ADDRSTRLEN);
    return std::string(buf);
}

std::string InetAddress::toIpPort() const
{
    char buf[INET_ADDRSTRLEN + 8];
    sprintf(buf, "%s:%u", toIp().c_str(), ntohs(addr_.sin_port));
    return std::string(buf);
}

函数实现解释:

  • 构造函数 InetAddress::InetAddress(uint16_t port, std::string_view ip)

    • 设置了 sockaddr_in 结构体的 sin_family 成员为 AF_INET,表示使用 IPv4 地址族。
    • 使用 htons() 函数将传入的 port(端口号)转换为网络字节序(Big-endian)。
    • 使用 inet_pton() 函数将传入的 ip(IPv4 地址字符串)转换为网络字节序的二进制格式,存储到 sockaddr_in 结构体的 sin_addr 成员中。
  • 成员函数 std::string InetAddress::toIp() const

    • 使用 inet_ntop() 函数将存储在 sockaddr_in 结构体的 sin_addr 中的网络字节序的 IPv4 地址转换为可读的点分十进制字符串格式,并返回该字符串。
  • 成员函数 std::string InetAddress::toIpPort() const

    • 调用 toIp() 函数获取点分十进制表示的 IP 地址字符串。
    • 使用 ntohs() 函数将 sockaddr_in 结构体中存储的网络字节序的端口号转换为主机字节序(Little-endian)的端口号。
    • 使用 sprintf() 函数将 IP 地址字符串和端口号格式化为 "ip:port" 的形式,并返回该字符串。

 

Channel.ixx

export module Channel;
export import EventLoop;
import nocopyable;
export import Timestamp;
import <functional>;
export import <memory>;
import <poll.h>;

export class Channel : nocopyable
{
public:
    using EventCallback = std::function<void()>;
    using ReadEventCallback = std::function<void(Timestamp)>;

    Channel(EventLoop *loop, int fd);
    ~Channel();

    void handleEvent(Timestamp receiveTime);

    void setReadCallback(ReadEventCallback cb) { readCallback_ = std::move(cb); }
    void setReadCallback(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; }
    bool isNoneEvent() const { return events_ == kNoneEvent; }
    bool isWriting() const { return events_ & kWriteEvent; }
    bool isReading() const { return events_ & kReadEvent; }
    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();
    }

    int index() { return index_; }
    void set_index(int idx) { index_ = idx; }

    EventLoop *ownerLoop() { return loop_; }
    void remove();

private:
    void handleEventWithGuard(Timestamp receiveTime);
    void update();

    static const int kNoneEvent;
    static const int kReadEvent;
    static const int kWriteEvent;
    EventLoop *loop_;
    const int fd_;
    int events_;
    int revents_;
    int index_;

    std::weak_ptr<void> tie_;
    bool tied_;

    ReadEventCallback readCallback_;
    EventCallback writeCallback_;
    EventCallback closeCallback_;
    EventCallback errorCallback_;
};

模块介绍

该模块名为 Channel,实现了一个用于管理文件描述符事件的类。它依赖于 EventLoop 类和其他一些头文件和类型,使用了 nocopyable 类作为基类。

Channel

  • 构造函数

    • Channel(EventLoop *loop, int fd);:接受一个指向 EventLoop 对象的指针和一个文件描述符作为参数,用于创建 Channel 类的实例。
  • 析构函数

    • ~Channel();:析构函数用于释放资源,清理对象。
  • 成员函数

    • void handleEvent(Timestamp receiveTime);:处理事件的方法,接收一个 Timestamp 参数。
    • 四个 set 函数用于设置不同类型事件的回调函数。
    • tie(const std::shared_ptr<void> &);:将 Channel 与给定的 shared_ptr 关联起来。
    • 多个操作函数用于控制事件的状态,如 enableReading()disableReading() 等。
    • remove():从 EventLoop 中移除当前 Channel
  • 私有函数

    • update():更新事件。
    • handleEventWithGuard(Timestamp receiveTime);:带有保护的事件处理函数。

 

Channel.cpp

import EventLoop;
import <sys/epoll.h>;
import Logger;
import Channel;
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)
{
}

Channel::~Channel() {}

void Channel::tie(const std::shared_ptr<void> &obj)
{
    tie_ = obj;
    tied_ = true;
}

void Channel::update()
{
    // 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);
        }
    }
    else
    {
        handleEventWithGuard(receiveTime);
    }
}

void Channel::handleEventWithGuard(Timestamp receiveTime)
{
    Logger &logger = Logger::instance();
    logger.log(Logger::LogLevel::DEBUG,
               "This is an informational message. Revents value: {}",
               std::to_string(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_();
        }
    }
}

模块导入和常量定义:

  • 使用了 EventLoopsys/epoll.hLoggerChannel 模块。
  • 定义了三个常量 Channel::kNoneEventChannel::kReadEventChannel::kWriteEvent,分别表示无事件、读事件和写事件,对应 EPOLLINEPOLLPRIEPOLLOUT

Channel 的函数实现:

  • 构造函数 Channel::Channel(EventLoop *loop, int fd)

    • 接受一个指向 EventLoop 对象的指针和一个文件描述符作为参数。
    • 初始化了 Channel 类的成员变量,如 events_revents_ 等。
  • 成员函数 void tie(const std::shared_ptr<void> &obj)

    • Channel 与给定的 shared_ptr 关联起来,标记为已关联状态。
  • 成员函数 void update()void remove()

    • 这两个函数被注释掉了,通常用于在 EventLoop 中更新和移除当前的 Channel
  • 成员函数 void handleEvent(Timestamp receiveTime)void handleEventWithGuard(Timestamp receiveTime)

    • handleEvent() 是对事件处理函数的调度器,检查是否与 shared_ptr 相关联,并调用 handleEventWithGuard()
    • handleEventWithGuard() 是对具体事件的处理函数,根据触发的事件类型执行相应的回调函数,如读、写、错误或关闭。

关于注释部分:

  • 代码中有一些注释掉的代码段,这里我还没调好,之后会更新,目前编译可以通过
  • 这里包括了日志记录和 Logger 类的使用。这些部分将被用于调试或记录日志,在生产环境中会被用于追踪事件处理过程中的状态和信息。

更新Logger

删除了Logger.cpp,简化了Logger模块的内容,使用更加直观

Logger.ixx(更新)

export module Logger;
export import <string>;
import <sstream>;
import <iostream>;
export class Logger
{
public:
    enum class LogLevel
    {
        INFO,
        ERROR,
        FATAL,
        DEBUG,
    };

    static Logger &instance()
    {
        static Logger logger;
        return logger;
    }

    void setLogLevel(LogLevel level)
    {
        logLevel_ = level;
    }

    template <typename... Args>
        requires(std::convertible_to<Args, std::string> && ...)
    void log(LogLevel level, const std::string &format, Args &&...args)
    {
        std::ostringstream oss;
        oss << "[" << static_cast<int>(level) << "] : ";
        ((oss << format << std::forward<Args>(args)), ...);
        std::string message = oss.str();

        std::cout << message << std::endl;
    }

private:
    LogLevel logLevel_ = LogLevel::INFO;
};

 总结

可以无所谓,不能无所获

                                        ——岩