项目构建
sylar使用makefile + cmake进行项目构建
有关cmake的介绍参考:自动化构建系统生成工具CMake_cmake 构造工具-CSDN博客
关键文件
sylar/cmake/utils.cmake
该文件为CMakeLists.txt提供了一组通用的构建工具函数
1.force_redefine_file_macro_for_sources(targetname)
为某个CMake target(通常是可执行文件)中的所有源文件单独设置 __FILE__ 宏为相对于项目根目录的路径,而不是默认的绝对路径。可以让日志中打印出来的 __FILE__ 更简洁(如 src/module/a.cpp 而不是 /home/user/project/src/module/a.cpp),便于调试和跨平台输出一致。
function(force_redefine_file_macro_for_sources targetname)
# 获取构建目标 targetname 中显式指定的源文件列表
get_target_property(source_files "${targetname}" SOURCES)
foreach(sourcefile ${source_files})
# 获取每个源文件当前已有的编译宏定义列表(如 -DXXX)
get_property(defs SOURCE "${sourcefile}" PROPERTY COMPILE_DEFINITIONS)
# 获取源文件的绝对路径
get_filename_component(filepath "${sourcefile}" ABSOLUTE)
# 获取源文件相对项目根目录(PROJECT_SOURCE_DIR)的路径
string(REPLACE ${PROJECT_SOURCE_DIR}/ "" relpath ${filepath})
# 向编译宏中追加 __FILE__="相对路径"
# 目的是让日志输出中的 __FILE__ 宏显示为项目内的相对路径,而非系统绝对路径
list(APPEND defs "__FILE__=\"${relpath}\"")
# 将更新后的宏定义列表重新设置到该源文件上
set_property(SOURCE "${sourcefile}" PROPERTY COMPILE_DEFINITIONS ${defs})
endforeach()
endfunction()
2.ragelmaker(src_rl outputlist outputdir)
为 Ragel 状态机文件(.rl)创建自定义构建步骤,调用 ragel 工具生成 .cc 文件,并将生成的路径加入到指定的输出列表变量中。
function(ragelmaker src_rl outputlist outputdir)
# 获取源 ragel 文件(.rl)的不带后缀文件名(NAME_WE 表示去除扩展名)
get_filename_component(src_file ${src_rl} NAME_WE)
# 设置 ragel 生成的 .cpp 文件的完整路径,例如 outputdir/test.rl.cc
set(rl_out ${outputdir}/${src_file}.rl.cc)
# 将生成的目标文件路径加入输出文件列表(注意要用 PARENT_SCOPE 把变量传回外部作用域)
set(${outputlist} ${${outputlist}} ${rl_out} PARENT_SCOPE)
# 添加一个自定义构建命令:
# - 生成文件为 ${rl_out}
# - 使用 ragel 编译 ${src_rl} 生成 ${rl_out}
# - 执行前先进入 outputdir 目录
# - 设置依赖为 ${src_rl},即源文件变更会触发重新生成
add_custom_command(
OUTPUT ${rl_out}
COMMAND cd ${outputdir}
COMMAND ragel ${CMAKE_CURRENT_SOURCE_DIR}/${src_rl} -o ${rl_out} -l -C -G2 --error-format=msvc
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${src_rl}
)
# 告诉 CMake 该文件是由构建步骤自动生成的
set_source_files_properties(${rl_out} PROPERTIES GENERATED TRUE)
endfunction(ragelmaker)
3.protobufmaker(src_proto outputlist outputdir)
为指定的 .proto 文件添加一个 CMake 自定义构建步骤,在构建过程中自动调用 protoc 编译器将该 .proto 文件生成对应的 .pb.cc 和 .pb.h 源码文件,并将生成的 .pb.cc 文件路径加入指定的变量中,供后续目标添加源文件使用。
function(protobufmaker src_proto outputlist outputdir)
# 从 proto 源文件路径中提取文件名(不带扩展名),用于输出文件命名
get_filename_component(src_file ${src_proto} NAME_WE)
# 提取 proto 文件所在的相对路径,便于构造输出目录结构
get_filename_component(src_path ${src_proto} PATH)
# 构造最终生成的 .pb.cc 文件的路径(注意路径中包含子目录结构)
set(protobuf_out ${outputdir}/${src_path}/${src_file}.pb.cc)
# 将生成的 .pb.cc 文件添加到调用者提供的变量中
# 注意:不能使用 list(APPEND),因为函数作用域不同,必须显式传回上层作用域
set(${outputlist} ${${outputlist}} ${protobuf_out} PARENT_SCOPE)
# 添加一个自定义构建命令:在编译时自动调用 protoc 编译器生成 .pb.cc 文件
add_custom_command(
OUTPUT ${protobuf_out} # 指定生成的目标文件
COMMAND protoc --cpp_out=${outputdir} -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${src_proto}
# 执行 protoc 命令,将 proto 文件生成对应的 .pb.cc/.pb.h 到指定目录
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/${src_proto} # 依赖 proto 文件本身
)
# 告诉 CMake:这个输出文件是构建过程中自动生成的,避免 IDE 提示或误删
set_source_files_properties(${protobuf_out} PROPERTIES GENERATED TRUE)
endfunction(protobufmaker)
4.sylar_add_executable(targetname srcs depends libs)
用于简洁高效地定义一个可执行文件的构建过程。它会创建一个可执行目标(add_executable)、指定它所依赖的其他目标(add_dependencies)、调用 force_redefine_file_macro_for_sources 函数为每个源文件设置 __FILE__ 宏路径(用于更好定位日志和错误信息),并链接指定的库(target_link_libraries)。
function(sylar_add_executable targetname srcs depends libs)
# 创建可执行文件 targetname,使用 srcs 作为源文件
add_executable(${targetname} ${srcs})
# 指定该可执行文件的构建依赖,必须在 depends 先构建完成之后才构建该目标
add_dependencies(${targetname} ${depends})
# 调用自定义函数 force_redefine_file_macro_for_sources,为每个源文件设置 __FILE__ 宏路径
force_redefine_file_macro_for_sources(${targetname})
# 指定目标要链接的库,如 pthread、protobuf、sylar 等
target_link_libraries(${targetname} ${libs})
endfunction()
sylar/CMakeLists.txt
基础配置
cmake_minimum_required(VERSION 3.5)
project(sylar C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_C_STANDARD 99)
set(CMAKE_CXX_FLAGS "$ENV{CXXFLAGS} -rdynamic -O3 -fPIC -ggdb -std=c++11 -Wall -Wno-deprecated -Wno-unused-function -Wno-builtin-macro-redefined -Wno-deprecated-declarations")
set(CMAKE_C_FLAGS "$ENV{CXXFLAGS} -rdynamic -O3 -fPIC -ggdb -std=c11 -Wall -Wno-deprecated -Wno-unused-function -Wno-builtin-macro-redefined -Wno-deprecated-declarations")
模块路径和工具函数引入
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
include(cmake/utils.cmake)
头文件与库路径配置
include_directories(. /apps/sylar/include)
link_directories(/apps/sylar/lib /apps/sylar/lib64)
外部依赖查找
find_package(Boost REQUIRED)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
endif()
find_package(MySQL REQUIRED)
if(MYSQL_FOUND)
include_directories(${MYSQL_INCLUDE_DIR})
endif()
find_package(Protobuf)
if(Protobuf_FOUND)
include_directories(${Protobuf_INCLUDE_DIRS})
endif()
find_package(OpenSSL REQUIRED)
if(OPENSSL_FOUND)
include_directories(${OPENSSL_INCLUDE_DIR})
endif()
find_package(ZLIB REQUIRED)
if(ZLIB_FOUND)
include_directories(${ZLIB_INCLUDE_DIR})
endif()
源文件收集与ragel/protobuf代码生成,生成的源文件也存入LIB_SRC中
set(LIB_SRC
sylar/address.cc
sylar/bytearray.cc
...
sylar/application.cc
sylar/zk_client.cc
)
ragelmaker(sylar/http/http11_parser.rl LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sylar/http)
ragelmaker(sylar/http/httpclient_parser.rl LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sylar/http)
ragelmaker(sylar/uri.rl LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR}/sylar)
protobufmaker(sylar/ns/ns_protobuf.proto LIB_SRC ${CMAKE_CURRENT_SOURCE_DIR})
构建共享库,将所有核心代码编译成一个共享库 libsylar.so,供测试程序和主程序链接使用。
add_library(sylar SHARED ${LIB_SRC})
force_redefine_file_macro_for_sources(sylar)
链接的三方库设置,汇总所有需要链接的第三方库,包括系统库、第三方静态库(如 hiredis、zookeeper)、数据库、加解密、内存分配等。
set(LIBS
sylar
dl
pthread
yaml-cpp
jsoncpp
${ZLIB_LIBRARIES}
${OPENSSL_LIBRARIES}
${PROTOBUF_LIBRARIES}
event
hiredis_vip
mysqlclient_r
zookeeper_mt
sqlite3
tinyxml2
jemalloc)
编译测试样例程序
sylar_add_executable(test_util "tests/test_util.cc" sylar "${LIBS}")
sylar_add_executable(test_hashmultimap "tests/test_hashmultimap.cc" sylar "${LIBS}")
...
sylar_add_executable(test_zkclient "tests/test_zookeeper.cc" sylar "${LIBS}")
sylar_add_executable(test_service_discovery "tests/test_service_discovery.cc" sylar "${LIBS}")
orm子模块构建
set(ORM_SRCS sylar/orm/table.cc sylar/orm/column.cc sylar/orm/index.cc sylar/orm/orm.cc sylar/orm/util.cc)
sylar_add_executable(orm "${ORM_SRCS}" sylar "${LIBS}")
把 tests/test_module.cc 这个源文件编译成一个共享库(动态链接库)test_module.so,它是作为插件模块来使用的。
add_library(test_module SHARED tests/test_module.cc)
构建最终主程序(执行入口),可执行文件命名为 sylar。
sylar_add_executable(bin_sylar "sylar/main.cc" sylar "${LIBS}")
set_target_properties(bin_sylar PROPERTIES OUTPUT_NAME "sylar")
设置所有生成的可执行文件输出到 bin/,共享库输出到 lib/,保持构建产物结构清晰。
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)
set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib)
sylar/Makefile
用于简化 CMake 项目的构建流程,它充当一个外部构建入口,用最少的命令让你快速编译整个项目或某个测试目标。
具体来说它通过定义默认目标和通配符目标,实现了一个自动化构建流程:当你在项目根目录下直接运行 make 时,它会检测是否存在 build/ 目录,如果存在就进入该目录并使用 make -j10 并行编译项目;如果不存在则会先创建 build/ 目录,然后再自动执行编译;当你运行 make xxx(如 make test_util)时,它会自动切换到 build/ 目录,构建指定的目标;如果 build/ 不存在,同样先配置再构建该目标。整个流程统一了构建过程,避免每次都手动输入 cmake 和 make 命令。
在初次构建项目时一般我们执行两次 make,即 make && make。第一次执行 make 会检测到当前没有 build/ 目录,于是会创建该目录并执行 cmake 配置过程,生成 Makefile 等构建文件;而这一步不会触发实际的编译操作。第二次执行 make 才会进入刚刚生成的 build/ 目录,并根据第一步生成的构建文件真正开始编译源码。因此,两次 make 分别对应 “生成构建系统” 和 “执行构建过程” 两个阶段。
.PHONY: xx
"":
if [ -d "build" ]; then \
cd build && make -j10; \
else \
mkdir build; \
cd build && cmake -DCMAKE_CXX_COMPILER:FILEPATH=$(shell which g++) -DCMAKE_C_COMPILER:FILEPATH=$(shell which gcc) ..; \
fi
# 通配目标,匹配 make xxx 时的情况
%:
if [ -d "build" ]; then \
cd build && make $@ -j10; \
else \
mkdir build; \
cd build && cmake -DCMAKE_CXX_COMPILER:FILEPATH=$(shell which g++) -DCMAKE_C_COMPILER:FILEPATH=$(shell which gcc) $@ ..; \
fi
项目环境的搭建
参考:captainwc/sylar
可以根据README.md中提供的sylar-build.pdf文件对项目依赖进行逐个安装,也可直接使用dockerfile进行sylar环境依赖的搭建
有关docker的介绍与使用可参考:docker介绍与常用命令汇总_docker简述-CSDN博客
提供的dockerfile做了以下事情
安装 Sylar 项目所需的依赖
编译并安装 hiredis-vip
解压并配置 ZooKeeper C 客户端
克隆 sylar 项目到 /workspace/sylar
最终进入 /workspace/sylar 并默认以 bash 进入容器
# Just ubuntu22.04 with some basic and useful tools, like g++ git bazel tmux uv etc
## You can get from https://raw.githubusercontent.com/captainwc/.dotfiles/refs/heads/main/scripts/docker/dockerfile-dev-base
FROM dev-base:latest
WORKDIR /workspace
RUN apt update \
&& apt install -y --no-install-recommends \
libjsoncpp-dev \
ragel \
libyaml-cpp-dev \
libmysqlclient-dev \
sqlite3 \
libsqlite3-dev \
redis-server \
libevent-dev \
libprotobuf-dev \
protobuf-compiler \
libssl-dev \
libtinyxml2-dev \
libjemalloc-dev \
libboost-dev \
&& git clone https://github.com/vipshop/hiredis-vip.git /tmp/hiredis-vip \
&& cd /tmp/hiredis-vip && make -j8 && make install && cd - \
&& sed -i '34i#include
&& wget -O /tmp/zookeeper.zip https://shuaikai-bucket0001.oss-cn-shanghai.aliyuncs.com/pic_bed/2025_4/zookeeper-client-c-3.10.0-ubuntu22.zip \
&& cd /tmp && unzip zookeeper.zip && mv zookeeper/include /usr/local/include/zookeeper && mv zookeeper/lib/* /usr/local/lib/ && cd - \
&& ln -sf /usr/local/lib/libzookeeper_st.so.2.0.0 /usr/local/lib/libzookeeper_st.so \
&& ln -sf /usr/local/lib/libzookeeper_st.so.2.0.0 /usr/local/lib/libzookeeper_st.so.2 \
&& ln -sf /usr/local/lib/libzookeeper_mt.so.2.0.0 /usr/local/lib/libzookeeper_mt.so \
&& ln -sf /usr/local/lib/libzookeeper_mt.so.2.0.0 /usr/local/lib/libzookeeper_mt.so.2 \
&& ln -sf /usr/lib/x86_64-linux-gnu/libmysqlclient.so.21.2.41 /usr/lib/x86_64-linux-gnu/libmysqlclient_r.so \
&& ldconfig -v \
&& git clone https://github.com/captainwc/sylar.git \
&& apt clean && rm -rf /var/lib/apt/lists/* /tmp/*
WORKDIR /workspace/sylar
CMD [ "/bin/bash" ]
使用该dockerfile
1.下载提供的基础镜像
wget https://raw.githubusercontent.com/captainwc/.dotfiles/main/scripts/docker/dockerfile-dev-base -O Dockerfile.dev-base
2.构建基础镜像
构建成功后,你本地就有了一个名为 dev-base:latest 的基础镜像,供后续使用。
docker build -t dev-base:latest -f Dockerfile.dev-base .
3.基于基础镜像构建适用于sylar项目的镜像
docker build -t sylar-dev -f dockerfile .
4.启动一个临时容器
进入已构建好的容器sylar-dev的bash shell,工作目录已设为 /workspace/sylar,代码也已经克隆好。
docker run -it --rm sylar-dev
5.编译sylar项目
make && make
性能测试
sylar测试了HttpServer的性能,并与nginx进行了对比。
视频:[C++高级教程]sylar服务器框架性能测试(sylar)_哔哩哔哩_bilibili
sylar
HttpServer端:
http_server_test.cc
#include "sylar/http/http_server.h"
#include "sylar/log.h"
sylar::Logger::ptr g_logger = SYLAR_LOG_ROOT();
void run() {
g_logger->setLevel(sylar::LogLevel::INFO);
sylar::Address::ptr addr = sylar::Address::LookupAnyIPAddress("0.0.0.0:8020");
if (!addr) {
SYLAR_LOG_ERROR(g_logger) << "get address error";
return;
}
sylar::http::HttpServer::ptr http_server(new sylar::http::HttpServer);
while (!http_server->bind(addr)) {
SYLAR_LOG_ERROR(g_logger) << "bind " << *addr << " fail";
sleep(1);
}
http_server->start();
}
int main(int argc, char** argv) {
sylar::IOManager iom(3);
iom.schedule(run);
return 0;
}
sylar/CMakeLists.txt
sylar_add_executable(my_http_server "sample/http_server_test.cc" sylar "${LIBS}")
HttpClient端:
使用的是工具ab进行测试,ab是Apache HTTP服务器附带的压测工具,用于测试 HTTP 服务的并发处理能力、QPS、响应时间等指标。
ubuntu需要先安装apache2-utils
sudo apt update
sudo apt install apache2-utils
ab -V
Centos需要先安装httpd-tools
sudo yum install httpd-tools
ab的使用
ab [选项] [http://目标URL/]
ab -n 1000000 -c 500 http://127.0.0.1:8020/
ab -n 1000000 -c 500 -k http://127.0.0.1:8020/
ab常用选项
选项作用说明-n <请求数>总共发出多少个 HTTP 请求-c <并发数>并发多少个连接同时进行请求-t <时间>持续测试多少秒(替代 -n,限时压测)-k使用 HTTP Keep-Alive(长连接)
sylar的HttpServer测试结果
短链接:Requests per second: 15645.17 [#/sec] (mean)
长链接:Requests per second: 47000.43 [#/sec] (mean) 这是什么意思
nginx
ubuntu中安装并启动nginx
sudo apt update
sudo apt install -y nginx
启动 Nginx(默认监听 80 端口)
sudo systemctl start nginx
sudo systemctl enable nginx
确认是否启动成功
sudo netstat -tunlp | grep nginx
ab压测
ab -n 1000000 -c 200 "http://127.0.0.1:80/sylar"
ab -n 1000000 -c 200 -k "http://127.0.0.1:80/sylar"
nginx的HttpServer测试结果
短链接:Requests per second: 16864.61 [#/sec] (mean)长链接:Requests per second: 43122.75 [#/sec] (mean)