• 重写muduo网络库开发环境和前期准备


    重写muduo网络库开发环境和前期准备

    muduo网络库核心模块TcpServer以epoll+线程池的方式实现,封装了和Linux线程、网络socket相关的十几个API,并没有考虑可移植性,只能在Linux环境下编译安装。

    我的开发环境为Ubuntu系统,项目编译用CMake环境编译,g++ (Ubuntu 9.4.0-1ubuntu1~20.04.1) 9.4.0。
    在这里插入图片描述

    muduo运用到了很多现代C++的编程技术和多线程编程理念,其中一些类对象、容器、智能指针、函数对象、绑定器使用了巧妙的设计非常值得我们学习。
    比如在muduo中有很多类都会以私有继承的方式继承这样一个类:
    在这里插入图片描述
    这个类把拷贝构造函数和赋值运算符重载函数都delete掉了,构造析构采用的是默认的方式。
    所有继承自noncopyable的类,在使用拷贝构造或者赋值的方式创建对象时,都会调用noncopyable的拷贝构造和赋值运算符重载,而这两种方式都被delete了,这使得所有继承noncopyable的类都不允许拷贝构造和赋值,可以帮我们省去很多重复的代码。

    noncopyable类

    重写我们自己的noncopyable.h:

    #pragma once
    
    //限制继承该类的类可以正常构造和析构,不能进行拷贝构造和赋值
    class noncopyable
    {
    public:
        noncopyable(const noncopyable&) = delete;
        noncopyable& operator=(const noncopyable&) = delete;
    protected:
        noncopyable() = default;
        ~noncopyable() = default;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    日志类Logger

    日志对于一个系统来说还是非常重要的,当软件运行以后,日志的输出可以帮我们定位很多问题,日志是我们定位处理问题最佳的途径了,我的日志类以单例对象的形式定义,且具有noncopyable属性。
    Logger.h

    #pragma once
    
    #include 
    
    #include "noncopyable.h"
    
    //LOG_INFO("%s %d",arg1,arg2)
    #define LOG_INFO(LogmsgFormat, ...) \
        do \
        { \
            Logger &logger = Logger::instance(); \
            logger.setLogLevel(INFO); \
            char buf[1024] = {0}; \
            snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
            logger.log(buf); \
        }while(0)
    
    #define LOG_ERROR(LogmsgFormat, ...) \
        do \
        { \
            Logger &logger = Logger::instance(); \
            logger.setLogLevel(INFO); \
            char buf[1024] = {0}; \
            snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
            logger.log(buf); \
        }while(0)
        
    #define LOG_FATAL(LogmsgFormat, ...) \
        do \
        { \
            Logger &logger = Logger::instance(); \
            logger.setLogLevel(INFO); \
            char buf[1024] = {0}; \
            snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
            logger.log(buf); \
            exit(-1); \
        }while(0)
    
    #ifdef MUDEBUG
    #define LOG_DEBUG(LogmsgFormat, ...) \
        do \
        { \
            Logger &logger = Logger::instance(); \
            logger.setLogLevel(INFO); \
            char buf[1024] = {0}; \
            snprintf(buf, 1024, LogmsgFormat, ##__VA_ARGS__); \
            logger.log(buf); \
        }while(0)
    #else
        #define LOG_DEBUG(LogmsgFormat, ...)
    #endif
    
    //定义日志的级别 INFO(跟踪过程)   ERROR(不影响程序执行的错误)   FATAL(致命错误)   DEBUG(调试信息)
    enum LogLevel
    {
        INFO,   //普通信息
        ERROR,  //错误信息
        FATAL,  //core信息
        DEBUG   //调试信息
    };
    
    //日志类
    class Logger : noncopyable
    {
    public:
        //获取日志单例对象
        static Logger& instance();
        //设置日志级别
        void setLogLevel(int level);
        //写日志
        void log(std::string msg);
    private:
        int logLevel_;	//日志级别
        Logger(){}	//单例私有构造函数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    Logger.cc

    #include "Logger.h"
    #include "Timestamp.h"
    
    #include 
    
    //获取日志单例对象
    Logger& Logger::instance()
    {
        static Logger logger;
        return logger;
    }
    
    //设置日志级别
    void Logger::setLogLevel(int level)
    {
        logLevel_ = level;
    }
    
    //写日志 格式:[级别信息] time : msg
    void Logger::log(std::string msg)
    {
        //打印日志类型
        switch (logLevel_)
        {
        case INFO:
            std:: cout << "[INFO]";
            break;
        case ERROR:
            std:: cout << "[ERROR]";
            break;
        case FATAL:
            std:: cout << "[FATAL]";
            break;
        case DEBUG:
            std:: cout << "[DEBUG]";
            break;
        
        default:
            break;
        }
    
        //打印时间
        std::cout << Timestamp::now().toString() << " : " << msg << std::endl;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    日志需要输出当前时间,再来看muduo的时间类。因为在muduo中,时间类Timestamp并不是其核心设计,我们就简单实现就好了。
    Timestamp.h

    #pragma once
    #include 
    #include 
    
    class Timestamp
    {
    public:
        Timestamp();
        //explicit表示不接受隐式转换
        explicit Timestamp(int64_t microSecondsSinceEpoch);
        static Timestamp now();
        std::string toString() const;
    private:
        int64_t microSecondsSinceEpoch_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Timestamp.cc

    #include "Timestamp.h"
    
    #include 
    
    Timestamp::Timestamp()
        :microSecondsSinceEpoch_(0)
        {}
    
    Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
        :microSecondsSinceEpoch_(microSecondsSinceEpoch)
        {}
    
    Timestamp Timestamp::now()
    {
        return Timestamp(time(NULL));
    }
    std::string Timestamp::toString() const
    {
        char buf[128] = {0};
        tm * tm_time = localtime(&microSecondsSinceEpoch_);
        snprintf(buf,128,"%4d/%02d/%02d %02d:%02d:%02d",
        tm_time->tm_year+1900,
        tm_time->tm_mon+1,
        tm_time->tm_mday,
        tm_time->tm_hour,
        tm_time->tm_min,
        tm_time->tm_sec);
        return buf;
    }
    
    //时间类测试
    // #include 
    // int main()
    // {
    //     std::cout <
        
    // }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    InetAddress类

    muduo的InetAddress类封装了socket地址类型sockaddr_in,IPv4和IPv6都支持,我们只需实现IPv4的封装即可。
    InetAddress.h

    #pragma once
    
    #include 
    #include 
    #include 
    
    class InetAddress
    {
    public:
        explicit InetAddress(uint16_t port = 0, std::string 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;
    
        const sockaddr_in* getSockAddr() const {return &addr_;}
    
        void setSockAddr(const sockaddr_in &addr) { addr_ = addr; }
    private:
        sockaddr_in addr_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    InetAddress.cc

    #include "InetAddress.h"
    
    #include 
    #include 
    #include 
    
    InetAddress::InetAddress(uint16_t port, std::string ip)
    {
        bzero(&addr_, sizeof addr_ );
        addr_.sin_family = AF_INET;
        addr_.sin_port = htons(port);
        addr_.sin_addr.s_addr = inet_addr(ip.c_str());
    }
    
    std::string InetAddress::toIp() const
    {
        char buf[64] = {0};
        ::inet_ntop(AF_INET, &addr_.sin_addr,buf,sizeof buf );
        return buf;
    }
    
    std::string InetAddress::toIpPort() const
    {
        char buf[64] = {0};
        ::inet_ntop(AF_INET, &addr_.sin_addr,buf,sizeof buf );
        size_t end = strlen(buf);
        uint16_t port = ntohs(addr_.sin_port);
        sprintf(buf+end, ":%u", port);
        return buf;
    }
    
    uint16_t InetAddress::toPort() const
    {
        return ntohs(addr_.sin_port);
    }
    // 测试
    // int main()
    // {
    //     InetAddress addr(8080);
    //     std::cout << addr.toIpPort() <
    //     return 0;
    // }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42

    Socket类

    muduo的Socket类封装了Socket类型和操作以及设置Tcp选项相关操作。
    重写Socket.h:

    #pragma once
    #include "noncopyable.h"
    
    class InetAddress;
    
    class Socket : noncopyable
    {
    public:
        explicit Socket(int sockfd)
            :sockfd_(sockfd)
        {}
        ~Socket();
    
        int fd() const { return sockfd_; }
        void bindAddress(const InetAddress &localaddr);
        void listen();
        int accept(InetAddress* peeraddr);
    
        void shutdownWrite();
    
        void setTcpNoDelay(bool on);
        void setReuseAddr(bool on);
        void setReusePort(bool on);
        void setKeepAlive(bool on);
    private:
        const int sockfd_;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    重写Socket.cc:

    #include "Socket.h"
    #include "Logger.h"
    #include "InetAddress.h"
    
    #include 
    #include 
    #include 
    #include 
    #include 
    
    Socket::~Socket()
    {
        close(sockfd_);
    }
    
    void Socket::bindAddress(const InetAddress &localaddr)
    {
        if(0 != bind(sockfd_, (sockaddr*)localaddr.getSockAddr(), sizeof(sockaddr_in)))
        {
            LOG_FATAL("bind sockfd:%d fail \n", sockfd_);
        }
    }
    void Socket::listen()
    {
        if(0 != ::listen(sockfd_, 1024))
        {
            LOG_FATAL("listen sockfd:%d fail \n", sockfd_);
        }
    }
    int Socket::accept(InetAddress* peeraddr)
    {
        /**
         * 1、accept函数参数不合法
         * 2、对返回的connfd没有设置非阻塞
         *      Reactor模型 one loop per thread  =》  poller + nonblocking IO
        sockaddr_in addr;
        bzero(&addr, sizeof addr);
        socklen_t len;
        int connfd = ::accept(sockfd_, (sockaddr*)&addr, &len);
        if(connfd >= 0)
        {
            peeraddr->setSockAddr(addr);
        }
        return connfd;
         */
        sockaddr_in addr;
        bzero(&addr, sizeof addr);
        socklen_t len = sizeof addr;
        int connfd = ::accept4(sockfd_, (sockaddr*)&addr, &len, SOCK_NONBLOCK | SOCK_CLOEXEC);
        if(connfd >= 0)
        {
            peeraddr->setSockAddr(addr);
        }
        return connfd;
    }
    
    void Socket::shutdownWrite()
    {
        if(::shutdown(sockfd_, SHUT_WR) < 0)
        {
            LOG_ERROR("socket::shotdownWrite error");
        }
    }
    
    void Socket::setTcpNoDelay(bool on)
    {
        int optval = on ? 1 : 0;   
        ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY, &optval, sizeof optval);
    }
    
    void Socket::setReuseAddr(bool on)
    {
        int optval = on ? 1 : 0;   
        ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof optval);
    }
    
    void Socket::setReusePort(bool on)
    {
        int optval = on ? 1 : 0;   
        ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof optval);
    }   
    
    void Socket::setKeepAlive(bool on)
    {
        int optval = on ? 1 : 0;   
        ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE, &optval, sizeof optval);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    前期准备完成,接下来就是muduo核心模块的编写了。

  • 相关阅读:
    文旅媒体有哪些?如何邀请到现场报道?
    Java基础学习笔记-4
    国外访问学者/博士后留学人员反诈骗指南
    【深度学习】详解 MAE
    多肽标签Avi Tag,GLNDIFEAQKIEWHE
    安全帽佩戴检测算法研究
    Java入门,最全面最简单的Java基础教程
    Spring创建和使用
    SQL语句中 LEFT JOIN 后 ON 和 WHERE 的区别
    【Linux】常见指令(二)
  • 原文地址:https://blog.csdn.net/weixin_43973403/article/details/126148846