gRPC is a modern, open source, high-performance remote procedure call (RPC) framework that can run anywhere. gRPC enables client and server applications to communicate transparently, and simplifies the building of connected systems.
C++开发者做什么事情都不太容易,网络编程原本已经很艰难了,想要使用gRPC来降低难度,库构建又是一道坎儿.这里展示如何在残酷的网络环境下使用CMake构建gRPC库,并附带示例验证库构建结果.
如何下载 gRPC 库源代码?
gRPC自身内容很庞大,依赖也比较复杂,如果使用Github下载,耗时费力不说,还容易中断.所幸 Gitee上提供了部分镜像仓库,可以将gRPC库自身内容快速拉取下来:
git clone -b v1.34.0 https://gitee.com/mirrors/grpc-framework.git grpc
复制代码
注意,目前在Gitee上只能找到gRPC依赖的部分"官方"镜像仓库,网友提供的镜像仓库较旧,因而只能构造v1.34.0版本.通过上述指令可以将v1.34.0版本的gRPC代码下载到grpc目录.
gRPC的依赖是通过git的submodules来关联的,代码下载下来之后可以看到.gitmodules文件,内部的git仓库地址都需要替换成Gitee的,例如:
[submodule "third_party/zlib"] path = third_party/zlib url = https://github.com/madler/zlib # When using CMake to build, the zlib submodule ends up with a # generated file that makes Git consider the submodule dirty. This # state can be ignored for day-to-day development on gRPC. ignore = dirty
复制代码
使用了zlib,在Gitee上搜索其代码仓库为https://gitee.com/mirrors/zlib,可以使用如下指令clone:
git clone https://gitee.com/mirrors/zlib.git
复制代码
因而替换成:
[submodule "third_party/zlib"] path = third_party/zlib #url = https://github.com/madler/zlib url = https://gitee.com/mirrors/zlib.git # When using CMake to build, the zlib submodule ends up with a # generated file that makes Git consider the submodule dirty. This # state can be ignored for day-to-day development on gRPC. ignore = dirty
复制代码
通过这种方法可以找到部分依赖库的最新镜像仓库,但是有一些找不到最新的,例如protobuf等库,用户local-grpc提供了gRPC依赖的全部代码仓库,可以使用这些仓库(注意代码不是同步镜像,导致gRPC只能构造相应版本),其中protobuf链接为:
https://gitee.com/local-grpc/protobuf.git
复制代码
这里将.gitmodules修改为如下内容即可:
[submodule "third_party/zlib"] path = third_party/zlib #url = https://github.com/madler/zlib url = https://gitee.com/mirrors/zlib.git # When using CMake to build, the zlib submodule ends up with a # generated file that makes Git consider the submodule dirty. This # state can be ignored for day-to-day development on gRPC. ignore = dirty[submodule "third_party/protobuf"] path = third_party/protobuf #url = https://github.com/google/protobuf.git url = https://gitee.com/local-grpc/protobuf.git[submodule "third_party/googletest"] path = third_party/googletest #url = https://github.com/google/googletest.git url = https://gitee.com/local-grpc/googletest.git[submodule "third_party/benchmark"] path = third_party/benchmark #url = https://github.com/google/benchmark url = https://gitee.com/mirrors/google-benchmark.git[submodule "third_party/boringssl-with-bazel"] path = third_party/boringssl-with-bazel #url = https://github.com/google/boringssl.git url = https://gitee.com/mirrors/boringssl.git[submodule "third_party/re2"] path = third_party/re2 #url = https://github.com/google/re2.git url = https://gitee.com/local-grpc/re2.git[submodule "third_party/cares/cares"] path = third_party/cares/cares #url = https://github.com/c-ares/c-ares.git url = https://gitee.com/mirrors/c-ares.git branch = cares-1_12_0[submodule "third_party/bloaty"] path = third_party/bloaty #url = https://github.com/google/bloaty.git url = https://gitee.com/local-grpc/bloaty.git[submodule "third_party/abseil-cpp"] path = third_party/abseil-cpp #url = https://github.com/abseil/abseil-cpp.git url = https://gitee.com/mirrors/abseil-cpp.git branch = lts_2020_02_25[submodule "third_party/envoy-api"] path = third_party/envoy-api #url = https://github.com/envoyproxy/data-plane-api.git url = https://gitee.com/local-grpc/data-plane-api.git[submodule "third_party/googleapis"] path = third_party/googleapis #url = https://github.com/googleapis/googleapis.git url = https://gitee.com/mirrors/googleapis.git[submodule "third_party/protoc-gen-validate"] path = third_party/protoc-gen-validate #url = https://github.com/envoyproxy/protoc-gen-validate.git url = https://gitee.com/local-grpc/protoc-gen-validate.git[submodule "third_party/udpa"] path = third_party/udpa #url = https://github.com/cncf/udpa.git url = https://gitee.com/local-grpc/udpa.git[submodule "third_party/libuv"] path = third_party/libuv #url = https://github.com/libuv/libuv.git url = https://gitee.com/mirrors/libuv.git
复制代码
使用如下指令拉取gRPC所有依赖:
cd grpcgit submodule update --init
复制代码
如果你希望使用CMake的FetchContent模块将gRPC整合到自身工程中,可以将上述步骤下载完成的完整源代码打包成压缩包,存放在自己的ftp等服务器上,然后使用如下Python脚本计算出SHA512:
import sysimport hashlib
# BUF_SIZE is totally arbitrary, change for your app!BUF_SIZE = 65536 # lets read stuff in 64kb chunks!
sha512 = hashlib.sha512()with open(sys.argv[1], 'rb') as f: while True: data = f.read(BUF_SIZE) if not data: break sha512.update(data)print("SHA512: {0}".format(sha512.hexdigest()))
复制代码
以压缩包完整/相对路径为参数,执行上述脚本,复制得到的SHA512内容,然后在你工程的CMakeLists.txt中以如下方式使用:
include(FetchContent)
FetchContent_Declare( gRPC URL "gRPC源码压缩包服务器路径" URL_HASH SHA512= "gRPC源码压缩包的SHA512" ##DOWNLOAD_DIR可以根据需要修改 DOWNLOAD_DIR ${CMAKE_SOURCE_DIR}/external/downloads/spdlog)set(FETCHCONTENT_QUIET OFF)FetchContent_MakeAvailable(gRPC)
##工程中库使用方式target_link_libraries(YourTarget grpc++)
复制代码
注意在之前要安装必备的依赖,例如 nasm.
不过上述使用方式构建时速度特别慢,以下展示如何直接构建出gRPC库,并安装到指定路径.
如何构建 gRPC 库
首先需要安装必要的依赖,例如在Windows上需要以下内容:
CMake的使用方式大同小异,这里以Windows为例展示,首先要进行配置,假设已经处于源代码路径中:
cmake -S . -B .build -G"Visual Studio 15 2017" -T v141 -A x64 -DgRPC_INSTALL=ON -DgRPC_BUILD_TESTS=OFF -DgRPC_BUILD_CSHARP_EXT=OFF -DCMAKE_INSTALL_PREFIX="安装路径"
复制代码
上述配置使用的是Visual Studio 2017及对应工具集,64 位构建,使能安装动作,禁止构造测试用例和C#扩展,并指定了安装路径.如果使用的是Visual Studio 2015则替换成Visual Studio 14 2015.
然后通过如下指令构建并安装:
cmake --build .build --target install --config Release
复制代码
经过一段时间的等待,在安装路径就可以看到构建出的结果了.
这里需要说明的是,gRPC无法将Debug和Release等多个配置安装到同一位置.开发者只能选择构建某一配置,然后在使用时工程构建也只能使用这一配置,通常可以选择Release构造,如果面临调试需求,可以选择RelWithDebInfo,即上述指令修改位:
cmake --build .build --target install --config RelWithDebInfo
复制代码
上述.build 路径为官方示例建议的路径,可以自行修改,但无必要.
经过上述操作,在安装路径下就有了可以使用的gRPC库了.下面来看一下如何使用它.
如何运行 Hello World
在gRPC源代码的\examples\cpp\helloworld路径下有如下代码文件:
greeter_server.ccgreeter_client.ccgreeter_async_server.ccgreeter_async_client.ccgreeter_async_client2.cc
复制代码
在\examples\protos下有对应的helloworld.proto文件.
将上述文件拷贝到示例目录,例如helloworld目录下,并添加CMakeList.txt工程配置,最终目录结构如下:
greeter_server.ccgreeter_client.ccgreeter_async_server.ccgreeter_async_client.ccgreeter_async_client2.cchelloworld.protoCMakeLists.txt
复制代码
将CMakeLists.txt修改为类似如下内容:
cmake_minimum_required(VERSION 3.15)#工程名,可自行修改project(grpc-examples CXX)
#以下三个find_package需要添加,否则找不到对应的target会报错find_package(Threads REQUIRED)#注意不要加CONFIGfind_package(protobuf CONFIG REQUIRED)find_package(gRPC CONFIG REQUIRED)
##添加共享的静态库,包含helloworld.proto中定义的RPC协议代码add_library(helloworld)target_sources(helloworld PRIVATE "helloworld.proto")
##生成helloworld.proto对应的C++代码protobuf_generate(TARGET helloworld LANGUAGE cpp)
##获取proto的grpc插件get_target_property(grpc_cpp_plugin_location gRPC::grpc_cpp_plugin LOCATION)
##生成helloworld.proto对应的gRPC-C++代码,以此来支持gRPC协议protobuf_generate(TARGET helloworld LANGUAGE grpc GENERATE_EXTENSIONS .grpc.pb.h .grpc.pb.cc PLUGIN "protoc-gen-grpc=${grpc_cpp_plugin_location}")
target_link_libraries(helloworld PRIVATE gRPC::grpc++)
##上述protobuf_generate将自动生成的代码存放于该位置,需要添加到include路径target_include_directories(helloworld PUBLIC ${CMAKE_CURRENT_BINARY_DIR})
##遍历列表创建应用程序foreach(_target greeter_client greeter_server greeter_async_client greeter_async_client2 greeter_async_server) add_executable(${_target} "${_target}.cc") target_link_libraries(${_target} PRIVATE helloworld gRPC::grpc++ gRPC::grpc++_reflection )endforeach()
复制代码
这里需要强调,官方文档在Windows下构建存在问题,必须添加gRPC::grpc++_reflection依赖,否则构建示例会报如下错误.
无法解析的外部符号 "void __cdecl grpc::reflection::InitProtoReflectionServerBuilderPlugin(void)
复制代码
CMake的protobuf_generate模块可以用来辅助代码生成动作,开发者只需要将.proto文件作为源代码添加到target中,然后protobuf_generate会根据配置自动生成对应代码,在.proto文件发生变化时能够自动刷新.详细信息参见gRPC and Plugin support in CMake.
在进行CMake配置时需要添加-DCMAKE_PREFIX_PATH=gRPC安装路径,这样find_package才能找到gRPC,例如:
cmake -S . -B build -G"Visual Studio 15 2017" -T v141 -A x64 -DCMAKE_PREFIX_PATH="gRPC安装路径"
复制代码
在输出目录找到生成的应用程序,例如greeter_server.exe:
然后启动客户端:
客户端输出如下内容并退出:
Greeter received: Hello world
复制代码
现在就可以查阅官方教程来学习gRPC了.
评论