【免费下载链接】30dayMakeCppServer
30天自制C++服务器,包含教程和源代码
项目地址:
https://gitcode.com/GitHub_Trending/30/30dayMakeCppServer
点击查看 免费下载
在前面的教程中,我们已经完成了主从 Reactor 多线程服务器核心架构的开发。从这一天开始,开发重心从"搭建架构"转向"打磨细节":用 CMake 取代手写 Makefile 管理编译链接,切换至 clang 编译器,并引入 clang-format、cpplint、clang-tidy 三件套统一代码风格、静态查错与性能建议。读完本文,你将掌握一个现代 C++ 网络库从源码组织、构建配置到自动代码审查的完整工程化流程,并能直接在当前仓库 code/day13 中复现这套流程。
为什么 C++ 项目一定要工程化
在 day12 之前,所有源文件都堆在同一个文件夹里、靠手写的 Makefile 逐个编译链接。随着模块越来越多,这座"代码屎山"的可读性、可维护性会迅速恶化。C++ 的编译、链接看似简单,实际上相当繁琐复杂(具体原理可参考《深入理解计算机系统(第三版)》第七章):头文件依赖、静态库/共享库、链接顺序、平台差异……如果没有构建工具,开发一个大型 C++ 项目,一半的时间会花在编译链接上。
CMake 正是为此而生:它使用简单、功能强大,能自动生成 Makefile(也支持 Ninja 等其他后端),让程序员把精力放回写代码本身。工程化不只解决"怎么编译",还解决"怎么保证代码质量"——这就是本文后半部分 format、cpplint、clang-tidy 三个工具存在的意义。
目录结构:源码库与测试程序分离
day13 把项目拆成了三个职责清晰的区域:
- src:核心网络库源码(.cpp 实现文件),本身不是一个可执行程序;
- src/include:所有头文件统一集中存放;
- test:使用网络库编写的测试程序(server、client 等),每个 .cpp 编译成一个可执行文件。
顶层 CMakeLists.txt 中通过如下方式声明头文件搜索路径:
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})
同时,根目录 CMakeLists 会拒绝在错误位置运行:它检查 ${PROJECT_BINARY_DIR}/CMakeLists.txt 是否存在,如果存在(说明你在项目根目录直接跑了 cmake)就报错提示必须"mkdir build; cd build; cmake ..",避免污染源码目录。
网络库编译为共享库
src 目录是网络库,没有 main 函数,因此把它所有 .cpp 编译链接成一个共享库 libpine_shared,见 src/CMakeLists.txt:
file(GLOB_RECURSE pine_sources ${PROJECT_SOURCE_DIR}/src/*.cpp)
add_library(pine_shared SHARED ${pine_sources})
file(GLOB_RECURSE …) 递归收集 src 下所有 .cpp,add_library(… SHARED …) 生成共享库,可被 test 目录下的可执行文件链接。
测试程序逐个编译为可执行文件
test/CMakeLists.txt 遍历 test 目录下每个 .cpp,用文件名(去掉 .cpp 后缀)作为目标名生成可执行文件:
foreach (pine_test_source ${PINE_TEST_SOURCES})
get_filename_component(pine_test_filename ${pine_test_source} NAME)
string(REPLACE ".cpp" "" pine_test_name ${pine_test_filename})
add_executable(${pine_test_name} EXCLUDE_FROM_ALL ${pine_test_source})
add_dependencies(build-tests ${pine_test_name})
add_dependencies(check-tests ${pine_test_name})
target_link_libraries(${pine_test_name} pine_shared)
set_target_properties(${pine_test_name}
PROPERTIES
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin"
COMMAND ${pine_test_name}
)
endforeach(pine_test_source ${PINE_TEST_SOURCES})
几个值得注意的细节:
- EXCLUDE_FROM_ALL 意味着这些可执行文件不会在裸 make 时全部构建,而是按需用 make server 等显式目标名单独编译;
- 每个可执行文件都通过 target_link_libraries 链接 pine_shared 共享库;
- RUNTIME_OUTPUT_DIRECTORY 把产物统一输出到 build/bin;
- build-tests / check-tests 两个自定义目标把它们串起来,方便批量构建与运行。
顶层 CMakeLists 还定义了产物目录与编译选项:
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)
共享库最终会落在 build/lib 中,而可执行文件在 build/bin。
按环境定制编译参数
不同环境下的编译参数在 CMake 中统一管理,见 CMakeLists.txt:
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fPIC -Wall -Wextra -std=c++17 -pthread")
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")
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")
- -std=c++17:启用 C++17 标准;
- -fPIC:生成位置无关代码,是编译共享库的必备选项,这里同时给可执行文件、共享库、静态库的链接都补上了;
- -Wall -Wextra:开启常见警告;-Wno-unused-parameter -Wno-attributes 暂时屏蔽两类噪音警告(源码注释标记了 #TODO: remove,等代码清理干净后应移除);
- -pthread:启用线程支持,day12 引入线程池后必须;
- Debug 模式下追加 -O0 -ggdb -fsanitize=address -fno-omit-frame-pointer -fno-optimize-sibling-calls:关闭优化、生成调试信息,并开启 AddressSanitizer(内存错误检测),同时保留完整调用栈,方便定位内存越界、泄漏等问题。
切换到 clang 编译器
注意:本版本起编译器从 GCC 切换到了更强大、更好用的 clang。clang 的编译错误与警告信息更清晰,并且它的配套工具链(clang-format、clang-tidy)正是本日工程化三件套的核心。CMake 通过 find_program 在若干常见路径中自动查找 clang-format、clang-tidy(见顶层 CMakeLists 的 PINE_CLANG_SEARCH_PATH,覆盖 /usr/local/bin、/usr/bin 以及 llvm 相关路径),找不到时只给出 WARNING 而不中断配置,保证灵活性。
三件套:format、cpplint、clang-tidy
配置好 CMake 和 clang 后,还需要做三件事,它们共同保证写出"风格一致、bug 较少、性能较好、遵守 google 编码规范"的项目:
DerivePointerAlignment: false
PointerAlignment: Right
ColumnLimit: 120
IncludeBlocks: Preserve
即在 Google 风格基础上,指针靠右对齐、单行上限 120 列(对应 cpplint 的 –linelength=120)、保留 include 块的原始顺序。
这三个工具都以 python 脚本的形式保存在 build_support 目录,可以一键自动运行:
build_support
– clang_format_exclusions.txt // 不需要格式化的代码
– run_clang_format.py // format
– cpplint.py // cpplint
– run_clang_tidy_extra.py // 帮助文件,不直接运行
– run_clang_tidy.py // clang-tidy
.clang-format // format配置
.clang-tidy // clang-tidy配置
三个工具在 CMake 中的集成方式
format:make format 调用 run_clang_format.py,对 src 和 test 下的源码就地重排格式(–fix),并跳过 clang_format_exclusions.txt 中列出的文件:
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
)
从脚本源码可以看出它的工作方式:遍历 –source_dirs 下所有 .h/.cpp 文件,剔除匹配排除 glob 的文件;带 –fix 时调用 clang-format -i 直接改写文件;不带 –fix(即 make check-format)时则逐个比对格式化前后的 diff,如有差异以非零退出码报告。${PINE_FORMAT_DIRS} 用 string(CONCAT …) 拼接为 src,test 逗号分隔形式传入脚本。
cpplint:make cpplint 先递归收集 src 与 test 下所有 .h/.cpp 作为检查对象,再通过管道分发给 cpplint 并行执行:
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
)
参数含义:xargs -n12 -P8 每 12 个文件一批、最多 8 个进程并行,兼顾启动开销与并发度;–verbose=2 输出详细信息;–linelength=120 与 clang-format 的 120 列保持一致;–filter 关闭了版权头、子目录 include 路径、casting 可读性三类与本项目无关的检查项。
clang-tidy:make clang-tidy 调用 LLVM 官方 run_clang_tidy.py 脚本,借助 CMake 生成的 compile_commands.json(CMAKE_EXPORT_COMPILE_COMMANDS)获得每个文件的真实编译参数:
add_custom_target(clang-tidy
${PINE_BUILD_SUPPORT_DIR}/run_clang_tidy.py
-clang-tidy-binary ${CLANG_TIDY_BIN}
-p ${CMAKE_BINARY_DIR}
)
clang-tidy 会自动向上查找父目录中的 .clang-tidy 配置文件。本项目的 .clang-tidy 启用了大量检查组:bugprone-*(易错点)、clang-analyzer-*(静态分析器)、concurrency-*(并发问题)、cppcoreguidelines-*、google-*、hicpp-*、modernize-*(现代 C++ 风格改造)、performance-*(性能建议)、portability-*、readability-*(可读性),并针对本网络库的实际情况禁用了若干规则(如 magic numbers、C 数组、vararg、显式转型等),同时通过 CheckOptions 强制命名规范:类/枚举/函数用 CamelCase、成员变量用小写加 _ 后缀、全局常量用 UPPER_CASE 等。WarningsAsErrors: '*' 把一切警告升级为错误,保证 make clang-tidy 一旦有告警就失败,迫使问题被立即修复。
完整构建流程:从零编译服务器
整套流程在仓库中可完整复现:
mkdir build
cd build
cmake ..
用 CMake 生成 Makefile。然后依次执行三个质量检查(将警告全部修改好后,重新运行直到全部通过):
make format # 代码格式化
make cpplint # google 规范静态检查
make clang-tidy # clang 深度代码分析
全部通过后,编译整个网络库(共享库会保存到 lib 文件夹中,此步不产生可执行文件)。需要可执行程序时,按目标名编译 test 目录下的对应源文件:
make server
make multiple_client
make single_client
生成的可执行文件位于 build/bin 目录下(注意:文档中描述的 build/test 目录对应旧版本布局,本仓库的 RUNTIME_OUTPUT_DIRECTORY 统一为 build/bin),此时运行 ./bin/server 即可启动服务器。以 test/server.cpp 为例,它的 main 只做了四件事:创建 EventLoop、创建 Server、进入 loop->Loop() 事件循环、退出时释放对象——业务逻辑全部封装在网络库内部。
工程化的实际收益:代码审查驱动的重构
今天不仅完成了"工具链"层面的工程化,这些工具给出的建议也直接推动了代码质量提升:
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);
像 Channel、EventLoop、Epoll、Server 等持有 fd、epoll 实例等独占资源的类,都在头文件中以 DISALLOW_COPY_AND_MOVE(Channel); 形式禁用拷贝与移动,从语言层面杜绝了资源的意外复制与悬垂问题。clang-tidy 的 cppcoreguidelines-*、hicpp-* 检查组正是这类规则的主要来源。
可以看到,performance-*、modernize-* 这类检查不仅是在"挑毛病",还能给出具体的性能优化方向。虽然目前还没有使用专门的性能测试工具,但处理速度、吞吐量、并发支持度都因为这些改动而明显提高——而这正是从"能跑的服务器"走向"工程化的服务器"的关键一步。
小结
day13 完成了三件事:用 CMake 重构了整个项目的构建系统(源码库与测试分离、网络库编译为共享库、测试按需编译);从 GCC 切换到 clang 并配置了分环境的编译参数;引入 clang-format、cpplint、clang-tidy 三件套并集成进 make format、make cpplint、make clang-tidy 一键目标。由此带来的 google-style 风格统一、不可拷贝/不可移动的类设计、引用传参优化,既减少了 bug 概率又提升了服务器性能。这套"构建 + 静态检查 + 深度分析"的工程化组合,也是后续 day14~day16 在细节上继续演进(支持业务逻辑自定义、跨平台适配、智能指针重构)的基础设施保障。
想要深入理解每个工具的具体行为,可以继续阅读仓库中的 顶层 CMakeLists.txt、.clang-format、.clang-tidy 以及 build_support 目录下的脚本源码。
赞
【免费下载链接】30dayMakeCppServer
30天自制C++服务器,包含教程和源代码
项目地址:
https://gitcode.com/GitHub_Trending/30/30dayMakeCppServer
点击查看 免费下载
相关推荐
机器学习推理系统实战指南:从基准测试到生产部署
Livox-SDK2激光雷达开发终极指南:从零开始的完整安装教程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网硕互联帮助中心


评论前必须登录!
注册