欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 手写muduo网络库(一):项目构建和时间戳、日志库

手写muduo网络库(一):项目构建和时间戳、日志库

2025/6/10 4:46:49 来源:https://blog.csdn.net/2301_76564925/article/details/148484821  浏览:    关键词:手写muduo网络库(一):项目构建和时间戳、日志库

引言

本文作为手写 muduo 网络库系列开篇,聚焦项目基础框架搭建与核心基础工具模块设计。通过解析 CMake 工程结构设计、目录规划原则,结合时间戳与日志系统的架构,为后续网络库开发奠定工程化基础。文中附完整 CMake 配置示例及模块代码。 

代码参考自:https://github.com/youngyangyang04/muduo-core
部分代码经过修改

一、项目工程化构建:从目录规划到编译配置

1. 分层目录结构设计

mymuduo/  
├─ src/          # 核心库源代码(实现文件)  ├─ CMakeLists.txt #
├─ include/      # 公共头文件(供外部引用)  
├─ build/        # 编译输出目录(生成库文件与可执行程序)  
├─ example/      # 示例程序(验证库功能)  ├─ CMakeLists.txt #
├─ CMakeLists.txt # 根目录编译配置  
└─ lib/          # 静态库/动态库输出目录(自动生成)  

设计说明:

  • src 与 include 分离:遵循 “接口与实现分离” 原则,头文件仅暴露必要 API,隐藏实现细节
  • build 独立输出:避免编译产物污染源码目录,支持cmake .. -B build的外部构建模式
  • example 验证层:通过具体场景测试库功能,便于快速调试与功能迭代

 2. 根目录 CMake 配置解析

cmake_minimum_required(VERSION 3.0)  
project(mymuduo)  # 设置C++11标准(muduo原生基于C++03,此处升级为现代C++)  
set(CMAKE_CXX_STANDARD 11)  
set(CMAKE_CXX_STANDARD_REQUIRED ON)  # 统一库文件输出路径  
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)  # 全局依赖库(如多线程支持)  
set(LIBS pthread)  # 模块化构建:递归编译子目录  
add_subdirectory(src)  
add_subdirectory(example)  

关键配置点:

  • CMAKE_CXX_STANDARD:强制要求 C++11 编译环境,支持 Lambda、智能指针等现代特性
  • LIBRARY_OUTPUT_PATH:集中管理库文件,便于后续集成时统一引用
  • add_subdirectory:通过分模块编译,实现 “核心库 - 示例程序” 的解耦构建

3. 核心库模块(src 目录)编译配置

# 自动收集当前目录所有.cpp文件  
file(GLOB SRC_FILES ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)  # 生成动态库(可通过SHARED/STATIC切换库类型)  
add_library(mymuduo SHARED ${SRC_FILES})  # 暴露头文件路径(供外部target引用)  
target_include_directories(mymuduo PUBLIC ${CMAKE_SOURCE_DIR}/include)  

设计考量:

  • 动态库优先:SHARED 模式便于运行时动态加载,适合需要频繁升级的库开发
  • PUBLIC 头文件:通过 PUBLIC 关键字,确保依赖 mymuduo 库的目标自动包含头文件路径

4. 示例程序(example 目录)编译配置 

file(GLOB EXAMPLE_SRCS ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)  
add_executable(testserver ${EXAMPLE_SRCS})  # 链接核心库与全局依赖  
target_link_libraries(testserver mymuduo ${LIBS})  # 编译选项配置  
target_compile_options(testserver PRIVATE -std=c++11 -Wall)  # 可执行文件输出到当前目录  
set_target_properties(testserver PROPERTIES  RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}  
)  

验证流程设计:

  • 独立编译目标:testserver 可执行程序直接依赖 mymuduo 库,方便单步调试
  • 编译选项增强:-Wall 开启严格编译警告,帮助提前发现潜在问题

 二、时间戳

1. 概述

时间戳(Timestamp)是一个表示特定时刻的数值,通常用于记录事件发生的时间。在本代码库中,Timestamp 类提供了一种简单的方式来处理时间戳,它可以获取当前时间的时间戳,并将时间戳转换为可读的字符串格式。

2. 类定义(Timestamp.h) 

#pragma once#include <iostream>
#include <string>
namespace mymuduo
{namespace base{class Timestamp{public:Timestamp();explicit Timestamp(int64_t microSecondsSinceEpoch);static Timestamp now();std::string toString() const;private:int64_t microSecondsSinceEpoch_;};}
}
  • 命名空间:代码使用了嵌套命名空间 mymuduo::base,这样可以避免命名冲突,提高代码的可维护性。
  • 构造函数
    • Timestamp():默认构造函数,将 microSecondsSinceEpoch_ 初始化为 0。
    • explicit Timestamp(int64_t microSecondsSinceEpoch):带参数的构造函数,使用传入的微秒数初始化 microSecondsSinceEpoch_explicit 关键字用于防止隐式类型转换。
  • 静态成员函数
    • static Timestamp now():静态函数,用于获取当前时间的 Timestamp 对象。
  • 成员函数
    • std::string toString() const:将时间戳转换为可读的字符串格式。

3. 类实现(Timestamp.cpp) 

#include <time.h>#include "Timestamp.h"
using namespace mymuduo::base;
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;
}//g++ -o test timestamp.cpp -I ../include  //g++编译命令
//测试代码
// #include <iostream>
// int main() {
//     std::cout << Timestamp::now().toString() << std::endl;
//     return 0;
// }
  • 构造函数实现
    • Timestamp():将 microSecondsSinceEpoch_ 初始化为 0。
    • Timestamp(int64_t microSecondsSinceEpoch):使用传入的微秒数初始化 microSecondsSinceEpoch_
  • now() 函数实现
    • 使用 time(NULL) 函数获取当前时间的秒数,并创建一个 Timestamp 对象返回。
  • toString() 函数实现
    • 使用 localtime() 函数将时间戳转换为本地时间的 tm 结构体。
    • 使用 snprintf() 函数将 tm 结构体中的年、月、日、时、分、秒格式化为字符串。
    • 返回格式化后的字符串。

4. 代码示例及编译指令

代码文件中提供了一个简单的测试示例,用于验证 Timestamp 类的功能:

#include <iostream>
int main() {std::cout << Timestamp::now().toString() << std::endl;return 0;
}

编译指令为:

g++ -o test timestamp.cpp -I ../include 

这个指令将 timestamp.cpp 文件编译成可执行文件 test,并指定头文件搜索路径为 ../include

 结果输出:

  三、日志

1. 整体概述

本日志系统采用了 iostream 风格,这与 muduo 原版日志有所不同。iostream 风格提供了一种直观且易于使用的方式来格式化和输出日志信息,通过重载 << 运算符,使得日志记录代码更加简洁和直观。

2. 核心组件

LogStream 类

LogStream 类是日志系统的核心,它负责格式化日志信息并将其输出。以下是 LogStream 类的关键特性:

  • 构造函数:在构造时,它会添加日志的基本信息,如时间戳、线程 ID、日志级别、文件名、行号和函数名。
LogStream::LogStream(Logger *loger, const char* file, int line, LogLevel l, const char* func)
:logger_(loger)
{const char* file_name = strrchr(file,'/');if(file_name){file_name = file_name + 1;}else{file_name = file;}stream_ << Timestamp::now().toString() << "[pid]:";if(thread_id == 0){thread_id = static_cast<pid_t>(::syscall(SYS_gettid));}stream_ << thread_id;stream_ << log_string[l];stream_ << "[" << file_name << ":" << line << "]";if(func){stream_ << "[" << func << "]";}
}
  • 析构函数:在析构时,它会添加换行符,并将格式化好的日志信息传递给 Logger 类进行输出。
LogStream::~LogStream()
{stream_ << "\n";if(logger_){logger_->Write(stream_.str());}else{std::cout << stream_.str() << std::endl ;}
}
  • 重载 << 运算符:通过模板函数重载 << 运算符,允许用户以 iostream 风格添加任意类型的数据到日志流中。
template<class T> LogStream& operator<<(const T& value)
{stream_ << value;return *this;
}

Logger 类(和muduo相同只实现最简单控制台的日志输出,可以修改为带有日志旋转的文件日志)

Logger 类负责管理日志级别和输出日志信息。它提供了以下接口:

  • SetLogLevel:设置日志级别。
void Logger::SetLogLevel(const LogLevel & level)
{level_ = level;
}
  • GetLogLevel:获取当前日志级别
LogLevel Logger::GetLogLevel() const
{return level_;
}
  • Write:将格式化好的日志信息输出到标准输出。
void Logger::Write(const std::string &msg)
{std::cout << msg;
}

3. 日志宏定义

为了方便使用,日志系统提供了一系列宏定义,用于不同级别的日志记录:

#define LOG_TRACE  \if(g_logger&&mymuduo::base::g_logger->GetLogLevel() <= mymuduo::base::kTrace) \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kTrace,__func__)#define LOG_DEBUG  \if(g_logger&&mymuduo::base::g_logger->GetLogLevel() <= mymuduo::base::kDebug) \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kDebug,__func__)#define LOG_INFO  \if(g_logger&&mymuduo::base::g_logger->GetLogLevel() <= mymuduo::base::kInfo) \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kInfo)#define LOG_WARN  \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kWarn)#define LOG_ERROR  mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kError)

4. 使用示例

以下是一个简单的使用示例,展示了如何使用日志系统:

int main()
{g_logger = new Logger();g_logger->SetLogLevel(kInfo);LOG_INFO << "test info";return 0;
}

 编译指令:

g++ -o testlog Logger.cpp Timestamp.cpp LogStream.cpp -I ../include

结果输出:

2025/06/06 17:38:15[pid]:8294 INFO [LogStream.cpp:66]test info

附录

 Timestamp.h

#pragma once#include <iostream>
#include <string>
namespace mymuduo
{namespace base{class Timestamp{public:Timestamp();explicit Timestamp(int64_t microSecondsSinceEpoch);static Timestamp now();std::string toString() const;private:int64_t microSecondsSinceEpoch_;};}
}

Timestamp.cpp

#include <time.h>#include "Timestamp.h"
using namespace mymuduo::base;
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;
}//g++ -o test timestamp.cpp -I ../include 
// #include <iostream>
// int main() {
//     std::cout << Timestamp::now().toString() << std::endl;
//     return 0;
// }

LogStream.h

#pragma once #include "Logger.h"#include <sstream>namespace mymuduo
{namespace base{extern Logger* g_logger;class LogStream{public:LogStream(Logger *loger, const char* file, int line, LogLevel l, const char* func=nullptr);~LogStream();template<class T> LogStream& operator<<(const T& value){stream_ << value;return *this;}private:std::ostringstream stream_;Logger* logger_{nullptr};};}
}#define LOG_TRACE  \if(g_logger&&mymuduo::base::g_logger->GetLogLevel() <= mymuduo::base::kTrace) \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kTrace,__func__)#define LOG_DEBUG  \if(g_logger&&mymuduo::base::g_logger->GetLogLevel() <= mymuduo::base::kDebug) \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kDebug,__func__)#define LOG_INFO  \if(g_logger&&mymuduo::base::g_logger->GetLogLevel() <= mymuduo::base::kInfo) \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kInfo)#define LOG_WARN  \mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kWarn)#define LOG_ERROR  mymuduo::base::LogStream(mymuduo::base::g_logger,__FILE__,__LINE__,mymuduo::base::kError)

 LogStream.cpp

#include "LogStream.h"
#include "Timestamp.h"#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <iostream>using namespace mymuduo::base;
Logger* mymuduo::base::g_logger = nullptr;static thread_local pid_t thread_id = 0;
const char* log_string[] = {" TRACE "," DEBUG "," INFO "," WARN "," ERROR ",
};LogStream::LogStream(Logger *loger, const char* file, int line, LogLevel l, const char* func)
:logger_(loger)
{const char* file_name = strrchr(file,'/');if(file_name){file_name = file_name + 1;}else{file_name = file;}stream_ << Timestamp::now().toString() << "[pid]:";if(thread_id == 0){thread_id = static_cast<pid_t>(::syscall(SYS_gettid));}stream_ << thread_id;stream_ << log_string[l];stream_ << "[" << file_name << ":" << line << "]";if(func){stream_ << "[" << func << "]";}
}LogStream::~LogStream()
{stream_ << "\n";if(logger_){logger_->Write(stream_.str());}else{std::cout << stream_.str() << std::endl ;}
}// //g++ -o testlog Logger.cpp Timestamp.cpp LogStream.cpp -I ../include
// int main()
// {
//     g_logger = new Logger();
//     g_logger->SetLogLevel(kInfo);
//     LOG_INFO << "test info";
//     return 0;
// }

Logger.h

#pragma once
#include "NonCopyable.h"#include <string>namespace mymuduo
{namespace base{enum LogLevel{kTrace,kDebug,kInfo,kWarn,kError,kMaxNumOfLogLevel,};class Logger: public NonCopyable{public:Logger() = default;~Logger() = default;void SetLogLevel(const LogLevel & level);LogLevel GetLogLevel() const;void Write(const std::string &msg);private:LogLevel level_ {kDebug};};}
}

Logger.cpp

#include "Logger.h"#include <iostream>using namespace mymuduo::base;void Logger::SetLogLevel(const LogLevel & level)
{level_ = level;
}LogLevel Logger::GetLogLevel() const
{return level_;
}void Logger::Write(const std::string &msg)
{std::cout << msg;
}

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com