引言
本文作为手写 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(µSecondsSinceEpoch_);
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(µSecondsSinceEpoch_);
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;
}