muduo中的noncopyable、logger、Timestamp、InetAddress

一、CMakeList.txt

cmake_minimum_required(VERSION 2.5)
project(mymuduo)

# mymuduo最终编译成so动态库,设置动态库的路径
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)

# 设置调试信息,添加编译选项,启动C++11语言标准
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -std=c++11")

# 定义参与编译的源代码文件, . 表示当前目录所有的源文件,把当前目录源文件组合起来用SRC_LIST记录
aux_source_directory(. SRC_LIST)

# 编译生成mymuduo动态库,最终会生成libmymuduo.so
add_library(mymuduo SHARED ${SRC_LIST})

二、noncopyable

muduo库中有很多类都是继承noncopyable,class默认是私有继承

在这里插入图片描述
noncopyable类的muduo源码如下:

namespace muduo{
    class noncopyable{
        public:
            noncopyable(const noncopyable&) = delete;    // 没有拷贝构造函数
            void operator=(const noncopyable&) = delete; // 没有赋值操作

        protected:
        	// protect表示派生类可以访问,而外部不可访问
            // 采用默认构造和析构
            noncopyable() = default;
            ~noncopyable() = default;
  };
}  // namespace muduo

把拷贝构造函数和赋值函数都delete,只使用默认的构造函数和析构函数(protect表示派生类可以访问,而外部不可访问

TcpServer的对象可以创建,调用基类的构造函数,基类的构造和析构是保护的,派生类是可以访问的,继承noncopyable的类对象可以构造和析构

由于基类的拷贝构造函数和赋值函数被delete了,那当我们对TcpServer的对象进行拷贝构造和赋值的时候,肯定要先调用基类部分的拷贝构造和赋值,然后才是派生类的拷贝构造和赋值,但是基类的拷贝构造和赋值已经删掉了,所以只要是继承了noncopyable的类都不可拷贝构造和赋值

重写noncopyable.h

#pragma once

/**
 * 继承noncopyable的类对象,可以进行构造和析构,无法进行拷贝构造和赋值操作
 */
class noncopyable{
    public:
        noncopyable(const noncopyable&) = delete;
        noncopyable& operator=(const noncopyable&) = delete;
    protected:
        noncopyable() = default;
        ~noncopyable() = default;
};

三、Logger

定义日志级别:

  1. INFO:打印重要的流程信息,比如muduo库随着客户端的连接、通信、断开的一些日志信息
  2. ERROR:打印一些不影响程序正常执行的错误
  3. FATAL:打印会让程序崩溃的错误,出现错误后输入关键信息程序就自行exit
  4. DEBUG:打印调试信息,一般来说较多,但是系统正常运行时会关闭这个选项。我们需要的时候会通过配置文件或者宏打开这个开关

Logger.h

#pragma once

#include <string>

#include "noncopyable.h"

// 正式项目中写宏的时候,为了防止产生错误,都会写成do...while(0),有多行的话,行末需要加上\,并且\后不能有空格
// LogMsgFormat时格式化字符串,##__VA_ARGS__用于获取可变参列表
#define LOG_INFO(LogMsgFormat, ...) \
    do{\
        Logger &logger = Logger::instance();\
        logger.set_log_level(INFO);\
        char buff[1024] = {0};\
        snprintf(buff, 1024, LogMsgFormat, ##__VA_ARGS__);\
        logger.log(buff);\
    }while(0)

#define LOG_ERROR(LogMsgFormat, ...) \
    do{\
        Logger &logger = Logger::instance();\
        logger.set_log_level(ERROR);\
        char buff[1024] = {0};\
        snprintf(buff, 1024, LogMsgFormat, ##__VA_ARGS__);\
        logger.log(buff);\
    }while(0)

#define LOG_FATAL(LogMsgFormat, ...) \
    do{\
        Logger &logger = Logger::instance();\
        logger.set_log_level(FATAL);\
        char buff[1024] = {0};\
        snprintf(buff, 1024, LogMsgFormat, ##__VA_ARGS__);\
        logger.log(buff);\
    }while(0)

// 由于调试信息通常较多,我们给出开关宏MUDUO_DEBUG
#ifdef MUDUO_DEBUG
#define LOG_DEBUG(LogMsgFormat, ...) \
    do{\
        Logger &logger = Logger::instance();\
        logger.set_log_level(DEBUG);\
        char buff[1024] = {0};\
        snprintf(buff, 1024, LogMsgFormat, ##__VA_ARGS__);\
        logger.log(buff);\
    }while(0)
#else
    #define LOG_DEBUG(LogMsgFormat, ...)
#endif

enum LogLevel{
    INFO,
    ERROR,
    FATAL,
    DEBUG,
};

// 日志类,写成单例,不需要进行拷贝构造和赋值
class Logger : noncopyable{
    public:
        // 获取唯一的日志实例对象
        static Logger& instance();
        // 设置日志级别
        void set_log_level(int level);
        // 写日志
        void log(std::string msg);
    private:
        int log_level_;
        // 单例模式需要将构造函数私有化
        Logger(){}
};

Logger.cc

#include <iostream>

#include "Logger.h"
#include "Timestamp.h"

// 获取唯一的日志实例对象
Logger& Logger::instance(){
    static Logger logger; // static保存在数据段,只会生成一个实例
    return logger;
}

// 设置日志级别
void Logger::set_log_level(int level){
    log_level_ = level;
}

// 写日志 [级别] time : msg
void Logger::log(std::string msg){
    switch (log_level_){
        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;
    }

    // 打印时间和msg
    std::cout<< Timestamp::now().toString() << " : " << msg << std::endl;
}

四、Timestamp

在这里插入图片描述

muduo网络库中,带参数的构造函数都加了explicit关键字,可以避免隐式类型转换

std::atomic_int Num1 = 10;  // error,先构造临时对象,再用临时对象拷贝构造Num1
std::atomic_int Num2(10);   // right,直接构造目标对象Num2

支持隐式类类型转换就是可以允许先构造一个临时对象,再用临时对象拷贝构造目标对象

加了explicit就不允许构造这个临时对象,必须直接构造目标对象

C++ explicit关键字用法详解

Timestamp.h

#pragma once

#include <iostream>
#include <string>

class Timestamp{
    public:
        Timestamp();
        explicit Timestamp(int64_t microSecondsSinceEpoch);
        static Timestamp now(); // now方法不需要通过对象调用,类名即可直接调用
        std::string toString() const;
    private:
        int64_t microSecondsSinceEpoch_;
};

在这里插入图片描述
注意tm_yeartm_mon的范围

Timestamp.cc

#include "Timestamp.h"

Timestamp::Timestamp()
    :microSecondsSinceEpoch_(0)
{}

Timestamp::Timestamp(int64_t microSecondsSinceEpoch)
    :microSecondsSinceEpoch_(microSecondsSinceEpoch)
{}

Timestamp Timestamp::now(){
    // 用time_t类型构造Timestamp类型,并返回Timestamp对象
    return Timestamp(time(NULL));
}

std::string Timestamp::toString() const{
    char buff[128] = {0};
    tm* tm_time = localtime(&microSecondsSinceEpoch_); // 时间戳转换为结构体tm类型
    snprintf(buff, 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 buff;
}

// #include <iostream>

// int main(){
//     Timestamp ts(time(NULL));
//     std::cout<<ts.toString()<<std::endl;
//     return 0;
// }

五、InetAddress

我们来看一下TcpServer中的参数InetAddress
在这里插入图片描述
在这里插入图片描述
我们前面看了noncopyable,现在看一下copyable

class copyable{
    protected:
        copyable() = default;
        ~copyable() = default;
};

就是使用默认的构造和析构函数,没有限制拷贝构造函数和operator=,我们的InetAddress类继承copyable,一眼就能知道InetAddress类是允许拷贝构造和赋值的

InetAddress.h

#pragma once

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

class InetAddress{
    public:
        explicit InetAddress(uint16_t port = 8888, 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;
		void setSockAddr(const sockaddr_in& addr) { addr_ = addr; }
		
    private:
        sockaddr_in addr_;  // 存储的都是网络字节序的协议信息
};

InetAddress.cc

#include "InetAddress.h"
#include <strings.h>
#include <string.h>

// 参数的默认值不管是声明还是定义,只能出现一次
InetAddress::InetAddress(uint16_t port, std::string ip){
    bzero(&addr_, sizeof(addr_));  // 清零addr_地址开始的sizeof(addr_)个字节
    addr_.sin_family = AF_INET;
    addr_.sin_port = htons(port);  // host to net ,本地字节序的port转成网络字节序的port,网络字节序都是大端,双方通信的时候需要都转换成统一的网络字节序,这样发送的数据就可以互相识别了
    addr_.sin_addr.s_addr = inet_addr(ip.c_str());  // inet_addr:字符串转成整型的网络字节序32位的ip地址

}

std::string InetAddress::toIP() const{
    // 从addr_返回ip地址
    char buff[64] = {0};
    inet_ntop(AF_INET, &addr_.sin_addr, buff, sizeof(buff));  // 从addr_.sin_addr读取32位整型的ip表示,转成本地字节序,存到buff
    return buff;
}

std::string InetAddress::toIpPort() const{
    // ip : port
    char buff[64] = {0};
    inet_ntop(AF_INET, &addr_.sin_addr, buff, sizeof(buff));
    size_t ip_end = strlen(buff);
    uint16_t port = ntohs(addr_.sin_port);
    sprintf(buff + ip_end, ":%u", port);
    return buff;
}

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

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

// #include <iostream>

// int main(){
//     InetAddress addr(8888);
//     std::cout << addr.toIpPort() << std::endl;
//     std::cout << addr.toIP() << std::endl;
//     std::cout << addr.toPort() << std::endl;
//     return 0;
// }

inet_ntopinet_pton

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
int inet_pton(int af, const char *src, void *dst);

inet_ntop:从src中读取网络字节序的整型表示的ip地址转化为点分十进制的字符串ip地址格式,存储到dst,size表示dst指向的缓冲区长度

返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1

inet_pton:将src点分十进制的字符串ip地址转化为用于网络字节序的数值格式,存储到dst指向的结构体

返回值:若成功则为指向结构的指针,若出错则为nullptr

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bugcoder-9905

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值