day13-工程化
该项目编译的过程都是使用CMake完成的,不过由于之前的文件都放在同一个文件夹中并没有分类。随着模块越来越多项目逐渐就变成了屎山,可读性极低,为了方便管理我们的项目这里开始使用CMake。
首先将项目的树结构进行更改
. .
|--Makefile |--CMakeLists.txt
|--ThreadPoolTest.cpp |--.clang-format
|--client.cpp |--.clang-tidy
|--server.cpp -----> |--.gitignore
|--src |--test
|--*.cpp |--CMakeLists.txt
|--*.h |--*.cpp
|--src
|--*.cpp
|--include
|--*.h
|--build_sport
|--*.py
|--clang_format_exclusions.txt
可以看出来结构化后文件更多了,但是大都是一些使用CMake及Clang的支持文件,而工程文件仍旧在src中,只是将头文件和cpp文件进行拆分。
首先还是看一下在工程文件中发生了什么变化。
1、Macros.h
在这个头文件中加入了一些宏定义,禁止了一些拷贝和移动操作
#pragma once
#define OS_LINUX
#define DISALLOW_COPY(cname) \
cname(const cname &) = delete; /* NOLINT */ \
cname &operator=(const cname &) = delete; /* NOLINT */
#define DISALLOW_MOVE(cname) \
cname(cname &&) = delete; /* NOLINT */ \
cname &operator=(cname &&) = delete; /* NOLINT */
#define DISALLOW_COPY_AND_MOVE(cname) \
DISALLOW_COPY(cname); \
DISALLOW_MOVE(cname);
2、Socket
在头文件中,包含了Macros.h将所有的类声明为不可拷贝、不可移动提高了项目的性能并减少bug,为了提高安全性还将Socket的有参构造函数设置为explicit(个人理解:因为int容易发生隐式转换)。
class InetAddress {
public:
InetAddress();
InetAddress(const char *ip, uint16_t port);
~InetAddress() = default;
DISALLOW_COPY_AND_MOVE(InetAddress);
void SetAddr(sockaddr_in addr);
sockaddr_in GetAddr();
const char *GetIp();
uint16_t GetPort();
private:
struct sockaddr_in addr_ {};
};
class Socket {
private:
int fd_{-1};
public:
Socket();
explicit Socket(int fd);
~Socket();
DISALLOW_COPY_AND_MOVE(Socket);
void Bind(InetAddress *addr);
void Listen();
int Accept(InetAddress *addr);
void Connect(InetAddress *addr);
void SetNonBlocking();
int GetFd();
};
2、高并发Epoll
除了将Epoll类声明为不可拷贝、不可移动外没什么改变
3、Channel(同上)
4、EventLoop
5、Acceptor
6、Connection(将设置回调函数的形参使用引用的方式完成)
7、Buffer
8、ThreadPool
9、服务器类
总结一下上述模块没有发生过大的变化,就是将类声明为不可拷贝、不可移动,将设置回调函数
的方法的形参都使用引用,并且将一些空的构造/析构函数=default,值得注意的是explicit一般只设置在易发生隐式转换的类构造前面(有参构造函数的参数较少的时候)。
10、CMake
需要在最外层和有执行文件生成的文件中加入CMakeLists.txt。
最外层CMakeLists.txt代码如下:
cmake_minimum_required(VERSION 3.10) //指定项目所需的CMake的最低版本
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) //生成编译命令的JSON文件
set(BUILD_SHARED_LIBS ON) //设置为生成共享库
set(CMAKE_CXX_STANDARD 17) //指定C++标准为C++17
set(CMAKE_CXX_STANDARD_REQUIRED ON) //指定C++标准为必需的
project(Pine
VERSION 0.1
DESCRIPTION "pine"
LANGUAGES C CXX
) //定义项目名称、版本、描述和所使用的编程语言(C和C++)
# 人们总是在错误的文件夹中运行 CMake,导致项目完全瘫痪或产生奇怪的 bug
# 这将检查你是否在已经有 CMakeLists.txt 的文件夹中运行 CMake
# 重要的是,它还会检查从根目录运行 CMake 的常见情况
//将CMakeLists.txt文件的完整路径存储在变量PATH_TO_CMAKELISTS_TXT中
file(TO_CMAKE_PATH "${PROJECT_BINARY_DIR}/CMakeLists.txt" PATH_TO_CMAKELISTS_TXT)
if (EXISTS "${PATH_TO_CMAKELISTS_TXT}")//检查是否存在CMakeLists.txt文件,如果存在则发出错误消息
message(FATAL_ERROR "Run CMake from a build subdirectory! \"mkdir build ; cd build ; cmake .. \" \
Some junk files were created in this folder (CMakeCache.txt, CMakeFiles); you should delete those.")
endif ()
# 预期目录结构
set(PINE_BUILD_SUPPORT_DIR "${CMAKE_SOURCE_DIR}/build_support") //定义变量PINE_BUILD_SUPPORT_DIR,指向构建支持目录
set(PINE_CLANG_SEARCH_PATH "/usr/local/bin" "/usr/bin" "/usr/local/opt/llvm/bin" "/usr/local/opt/llvm@8/bin" "/usr/local/Cellar/llvm/8.0.1/bin") //指定查找Clang工具的路径
if (NOT DEFINED CLANG_FORMAT_BIN)//如果未定义变量CLANG_FORMAT_BIN,则查找并设置Clang格式化工具的路径
# attempt to find the binary if user did not specify
find_program(CLANG_FORMAT_BIN
NAMES clang-format clang-format-8
HINTS ${PINE_CLANG_SEARCH_PATH})
endif ()
if ("${CLANG_FORMAT_BIN}" STREQUAL "CLANG_FORMAT_BIN-NOTFOUND")//如果未找到Clang格式化工具,则发出警告消息
message(WARNING "Pine/main couldn't find clang-format.")
else ()
message(STATUS "Pine/main found clang-format at ${CLANG_FORMAT_BIN}")
endif ()
if (NOT DEFINED CLANG_TIDY_BIN)//如果未定义变量CLANG_TIDY_BIN,则查找并设置Clang静态分析工具的路径
# attempt to find the binary if user did not specify
find_program(CLANG_TIDY_BIN
NAMES clang-tidy clang-fidy-8
HINTS ${PINE_CLANG_SEARCH_PATH})
endif ()
if ("${CLANG_TIDY_BIN}" STREQUAL "CLANG_TIDY_BIN-NOTFOUND")//如果未找到Clang静态分析工具,则发出警告消息
message(WARNING "Pine/main couldn't find clang-tidy.")
else ()
# 输出编译命令
set(CMAKE_EXPORT_COMPILE_COMMANDS 1)
message(STATUS "Pine/main found clang-fidy at ${CLANG_TIDY_BIN}")
endif ()
# cpplint(用以检查代码是否符合Google风格)
find_program(CPPLINT_BIN
NAMES cpplint cpplint.py
HINTS ${PINE_BUILD_SUPPORT_DIR})//查找并设置cpplint工具的路径
if ("${CPPLINT_BIN}" STREQUAL "CPPLINT_BIN-NOTFOUND") //如果未找到cpplint工具,则发出警告消息
message(WARNING "Pine/main couldn't find cpplint.")
else ()
message(STATUS "Pine/main found cpplint at ${CPPLINT_BIN}")
endif ()
# Compiler flags.
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wextra -std=c++17 -pthread") //设置C++编译器标志
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-unused-parameter -Wno-attributes") #TODO: remove
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls") //设置调试时的C++编译器标志
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIC") //设置可执行文件链接器标志
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fPIC") //设置共享库链接器标志
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -fPIC") //设置静态库链接器标志
set(GCC_COVERAGE_LINK_FLAGS "-fPIC") //设置GCC覆盖率链接标志
message(STATUS "CMAKE_CXX_FLAGS: ${CMAKE_CXX_FLAGS}")
message(STATUS "CMAKE_CXX_FLAGS_DEBUG: ${CMAKE_CXX_FLAGS_DEBUG}")
message(STATUS "CMAKE_EXE_LINKER_FLAGS: ${CMAKE_EXE_LINKER_FLAGS}")
message(STATUS "CMAKE_SHARED_LINKER_FLAGS: ${CMAKE_SHARED_LINKER_FLAGS}")
# Output directory.
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) //设置存档输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) //设置库输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) //设置运行时输出目录
# Includes.
set(PINE_SRC_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/src/include) //定义源文件包含目录
set(PINE_TEST_INCLUDE_DIR ${PROJECT_SOURCE_DIR}/test/include) //定义测试文件包含目录
include_directories(${PINE_SRC_INCLUDE_DIR} ${PINE_TEST_INCLUDE_DIR}) //包含源文件和测试文件的目录
add_subdirectory(src) //添加子目录src
add_subdirectory(test) //添加子目录test
string(CONCAT PINE_FORMAT_DIRS
"${CMAKE_CURRENT_SOURCE_DIR}/src,"
"${CMAKE_CURRENT_SOURCE_DIR}/test,"
) //将源文件和测试文件的目录存储在变量PINE_FORMAT_DIRS中
# 运行 clang 格式并就地更新文件
add_custom_target(format ${PINE_BUILD_SUPPORT_DIR}/run_clang_format.py
${CLANG_FORMAT_BIN}
${PINE_BUILD_SUPPORT_DIR}/clang_format_exclusions.txt
--source_dirs
${PINE_FORMAT_DIRS}
--fix
--quiet
) //添加自定义目标format,用于运行clang-format格式化源文件
# 运行 clang 格式,如果有文件需要重新格式化,则以非零退出代码
add_custom_target(check-format ${PINE_BUILD_SUPPORT_DIR}/run_clang_format.py
${CLANG_FORMAT_BIN}
${PINE_BUILD_SUPPORT_DIR}/clang_format_exclusions.txt
--source_dirs
${PINE_FORMAT_DIRS}
--quiet
)//添加自定义目标check-format,用于检查源文件格式
file(GLOB_RECURSE PINE_LINT_FILES
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp"
"${CMAKE_CURRENT_SOURCE_DIR}/test/*.h"
"${CMAKE_CURRENT_SOURCE_DIR}/test/*.cpp"
)//在当前源代码目录下递归地查找所有的.h和.cpp文件,并将它们的路径存储在名为PINE_LINT_FILES的变量中,分别表示在src目录和test目录下匹配.h和.cpp文件
# 平衡之举:cpplint.py 启动需要大量时间、
# 所以每次调用要处理 12 个文件,同时还要确保并行性
add_custom_target(cpplint echo '${PINE_LINT_FILES}' | xargs -n12 -P8
${CPPLINT_BIN}
--verbose=2 --quiet
--linelength=120
--filter=-legal/copyright,-build/include_subdir,-readability/casting
) //运行cpplint工具对之前通过file指令收集到的C++源文件进行代码风格检查
###########################################################
# "make clang-tidy" target
###########################################################
# 运行 clang-tidy 并在发现任何错误时以非零退出代码退出。
# 注意,clang-tidy 会自动在父目录中查找 .clang-tidy 文件
add_custom_target(clang-tidy
${PINE_BUILD_SUPPORT_DIR}/run_clang_tidy.py # run LLVM's clang-tidy script
-clang-tidy-binary ${CLANG_TIDY_BIN} # using our clang-tidy binary
-p ${CMAKE_BINARY_DIR} # using cmake's generated compile commands
)//运行clang-tidy工具对项目的C++代码进行静态代码分析和检查
test中的CMakeLists.txt代码如下:
file(GLOB PINE_TEST_SOURCES "${PROJECT_SOURCE_DIR}/test/*.cpp")//查找${PROJECT_SOURCE_DIR}/test/下的所有.cpp文件,并将它们的路径保存在名为PINE_TEST_SOURCES的变量中
add_custom_target(build-tests COMMAND ${CMAKE_CTEST_COMMAND} --show-only)//只显示测试案例,而不实际运行它们
add_custom_target(check-tests COMMAND ${CMAKE_CTEST_COMMAND} --verbose)//运行测试案例
foreach (pine_test_source ${PINE_TEST_SOURCES})//这行代码开始一个foreach循环,遍历之前找到的测试源文件路径
# Create a human readable name.
get_filename_component(pine_test_filename ${pine_test_source} NAME)//提取了每个测试源文件的文件名,并将其存储在名为pine_test_filename的变量中
string(REPLACE ".cpp" "" pine_test_name ${pine_test_filename})//将.cpp文件扩展名替换为空,得到了每个测试的名称,并将其存储在名为pine_test_name的变量中
add_executable(${pine_test_name} EXCLUDE_FROM_ALL ${pine_test_source})//创建一个可执行目标,其中${pine_test_name}是测试的名称,${pine_test_source}是测试的源文件路径。EXCLUDE_FROM_ALL标志意味着这些测试不会被默认构建,只有在明确指定时才会构建
add_dependencies(build-tests ${pine_test_name})
add_dependencies(check-tests ${pine_test_name})
//这两行代码将每个测试目标添加为build-tests和check-tests自定义目标的依赖关系,确保在运行这些目标时先构建相应的测试
target_link_libraries(${pine_test_name} pine_shared)//指定了测试目标${pine_test_name}需要链接的库,其中pine_shared是项目中的共享库
# Set test target properties and dependencies.
set_target_properties(${pine_test_name}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
COMMAND ${pine_test_name}
)//这行代码设置了测试目标${pine_test_name}的属性,包括运行时输出目录和要执行的命令
endforeach(pine_test_source ${PINE_TEST_SOURCES})
这部分不一定要看感觉,但是为了能够构建工程还是很必要的,学习一下感觉面试也能吹吹,源代码在30天构造服务器中