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 grpc
git submodule update --init如果你希望使用CMake的FetchContent模塊將gRPC整合到自身工程中,可以將上述步驟下載完成的完整原始碼打包成壓縮包,存放在自己的ftp等伺服器上,然後使用如下Python腳本計算出SHA512:
import sys
import 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上需要以下內容:
Visual Studio 2015或Visual Studio 2017CMake的使用方式大同小異,這裡以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.cc
greeter_client.cc
greeter_async_server.cc
greeter_async_client.cc
greeter_async_client2.cc在\examples\protos下有對應的helloworld.proto文件.
將上述文件拷貝到示例目錄,例如helloworld目錄下,並添加CMakeList.txt工程配置,最終目錄結構如下:
greeter_server.cc
greeter_client.cc
greeter_async_server.cc
greeter_async_client.cc
greeter_async_client2.cc
helloworld.proto
CMakeLists.txt將CMakeLists.txt修改為類似如下內容:
cmake_minimum_required(VERSION 3.15)
#工程名,可自行修改
project(grpc-examples CXX)
#以下三個find_package需要添加,否則找不到對應的target會報錯
find_package(Threads REQUIRED)#注意不要加CONFIG
find_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.
在輸出目錄找到生成的應用程式,例如greeter_server.exe:
./greeter_server.exe然後啟動客戶端:
./greeter_client.exe客戶端輸出如下內容並退出:
Greeter received: Hello world現在就可以查閱官方教程來學習gRPC了.