注:该文用于个人学习记录和知识交流,如有不足,欢迎指点。

一、环境搭建

1. 下载源码

git clone https://github.com/grpc/grpc

2. 查看版本并选择合适的版本,这里选择v1.45.2相对较新的版本

git tag
git checkout v1.45.2

查看此时grpc目录内容的大小du -h --max-depth=1, 可以看到427M左右

ubuntu@VM-16-11-ubuntu:~/rpc/grpc$ du -h --max-depth=1
348M    ./.git
32K ./summerofcode
1.5M    ./doc
6.5M    ./tools
4.0K    ./spm-core-include
24M ./test
80K ./cmake
3.0M    ./third_party
4.0K    ./spm-cpp-include
1.5M    ./templates
8.0K    ./.bazelci
1.9M    ./include
5.0M    ./examples
34M ./src
268K    ./etc
64K ./.github
284K    ./bazel
427M    .

3. 下载第三方依赖库

git submodule update --init

下载完后会发现整个grpc目录内容明显变大

再次查看 目录大小,占用了1.3G

ubuntu@VM-16-11-ubuntu:~/rpc/grpc$ du -h --max-depth=1
899M    ./.git
32K ./summerofcode
1.5M    ./doc
6.5M    ./tools
4.0K    ./spm-core-include
24M ./test
80K ./cmake
291M    ./third_party
4.0K    ./spm-cpp-include
1.5M    ./templates
8.0K    ./.bazelci
1.9M    ./include
5.0M    ./examples
34M ./src
268K    ./etc
64K ./.github
284K    ./bazel
1.3G    

4. 编译和安装

mkdir -p cmake/build
cd cmake/build
cmake ../..
make
sudo make install

二、protobuf安装

不用手动安装protobuf,不然版本可能和grcp不匹配,必须在 grpc 执行 git submodule update --init 命令之后生成的 third_party/protobuf 里面编译安装对应的 protobuf。

cd third_party/protobuf/
./autogen.sh 
./configure --prefix=/usr/local
make

sudo make install
sudo ldconfig  # 使得新安装的动态库能被加载

protoc --version
显示3.19.4

三、测试环境

编译helloworld

cd grpc/examples/cpp/helloworld/
mkdir build
cd build/
cmake ..
make登录后复制

启动服务和客户端

# 启动服务端,监听在50051端口
./greeter_server
Server listening on 0.0.0.0:50051
# 启动客户端,服务端返回Hello world
./greeter_client 
Greeter received: Hello world

四、CMakeLists.txt模板

需修改的两处

  • 此例针对IM.Login.proto,可将IM.Login修正为实际协议名,且支持多协议生成。
  • for_each可以同时生成多个依赖proto的可执行文件(示例中为im_login_client.cc、 
      im_login_server.cc、im_login_async_server.cc)
# 1. 最低CMake版本
cmake_minimum_required(VERSION 3.10)

# 2. 项目名
project(grpc_demo CXX)

# 3. C++标准
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# set(CMAKE_BUILD_TYPE Debug) #生成可调试的文件


# 4. 查找核心依赖(注意使用 CONFIG 模式)
find_package(Protobuf REQUIRED)
find_package(gRPC CONFIG REQUIRED) # 这里加上 CONFIG

# 5. 定义工具和库目标
# Protobuf
set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
set(_REFLECTION gRPC::grpc++_reflection)
if(CMAKE_CROSSCOMPILING)
  find_program(_PROTOBUF_PROTOC protoc)
else()
  set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
endif()

# gRPC
set(_GRPC_GRPCPP gRPC::grpc++)
if(CMAKE_CROSSCOMPILING)
  find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
else()
  set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
endif()



# 6. 配置proto文件
set(PROTO_FILES
    ${CMAKE_CURRENT_SOURCE_DIR}/IM.Login.proto
    #${CMAKE_CURRENT_SOURCE_DIR}/public.proto #支持多协议生成,比如在IM.Login.proto中import public.proto了
)
set(GENERATED_SRCS "") # 用于收集所有生成的 .pb.cc 和 .grpc.pb.cc


# 7. 生成proto和grpc代码
foreach(PROTO_FILE ${PROTO_FILES})
    # 步骤1:获取带后缀的完整文件名(如 IM.Login.proto)
    get_filename_component(PROTO_FILENAME ${PROTO_FILE} NAME)
    
    # 步骤2:手动替换掉.proto后缀,得到基础名(IM.Login.proto → IM.Login)
    string(REPLACE ".proto" "" PROTO_BASE_NAME ${PROTO_FILENAME})
    
    # 步骤3:定义生成的文件路径(确保是 IM.Login.pb.cc)
    set(PB_CC "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_BASE_NAME}.pb.cc")
    set(PB_H "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_BASE_NAME}.pb.h")
    set(GRPC_CC "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_BASE_NAME}.grpc.pb.cc")
    set(GRPC_H "${CMAKE_CURRENT_BINARY_DIR}/${PROTO_BASE_NAME}.grpc.pb.h")
    
    # 加入总列表
    list(APPEND GENERATED_SRCS ${PB_CC} ${GRPC_CC})
    
    # 生成命令
    add_custom_command(
        OUTPUT ${PB_CC} ${PB_H} ${GRPC_CC} ${GRPC_H} #如果有这些文件了,就不会再生成
        COMMAND ${_PROTOBUF_PROTOC}
        ARGS --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
             --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
             --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
             -I "${CMAKE_CURRENT_SOURCE_DIR}" #-I 表示proto所在的目录
             ${PROTO_FILE}
        DEPENDS ${PROTO_FILE} #如果当前文件修改了重新生成当前文件。 注意:不同协议间有import需要改为 DEPENDS ${PROTO_FILES},这样, 改了任何一个 proto(包括 public.proto)都重新生成
        COMMENT "Generating code for ${PROTO_BASE_NAME}.proto"
    )
endforeach()




# 8. 批量编译多个目标
foreach(_target
  im_login_client 
  im_login_server
  im_login_async_server)
  add_executable(${_target} 
    "${_target}.cc"
    ${GENERATED_SRCS}  # 链接所有生成的文件
  )
  
  target_include_directories(${_target} 
    PRIVATE
    ${CMAKE_CURRENT_BINARY_DIR}
    ${CMAKE_CURRENT_SOURCE_DIR}
  )
  
  target_link_libraries(${_target}
    PRIVATE
    ${_REFLECTION}
    ${_GRPC_GRPCPP}
    ${_PROTOBUF_LIBPROTOBUF}
    pthread
  )
endforeach()

# 可选:设置输出目录
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR})

补充:

变量 含义
CMAKE_CURRENT_SOURCE_DIR 当前处理的 CMakeLists.txt 所在的源码目录(即源代码所在路径)。
CMAKE_CURRENT_BINARY_DIR 当前处理的 CMakeLists.txt 对应的构建目录(即编译生成文件的路径)。

通俗示例假设项目结构如下,且你在 build/ 目录下执行 cmake ..源码外构建):

project/                # 根目录
  CMakeLists.txt        # 根 CMakeLists.txt
  src/
    CMakeLists.txt      # 子目录 CMakeLists.txt
    main.cpp
  build/                  # 构建目录(你在这里运行 cmake)

此时变量值为:

  • 处理根 CMakeLists.txt 时:
    • CMAKE_CURRENT_SOURCE_DIR = /path/to/project
    • CMAKE_CURRENT_BINARY_DIR = /path/to/project/build
  • 处理 src/CMakeLists.txt 时:
    • CMAKE_CURRENT_SOURCE_DIR = /path/to/project/src
    • CMAKE_CURRENT_BINARY_DIR = /path/to/project/build/src

常见用途

  • CMAKE_CURRENT_SOURCE_DIR:用于引用源码文件,如 ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
  • CMAKE_CURRENT_BINARY_DIR:用于引用生成的文件(如配置头文件、编译输出),如 ${CMAKE_CURRENT_BINARY_DIR}/config.h
Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐