引言
本文作為手寫 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;
}