简介:这份专为 Visual Studio 2015 准备的最新版 gRPC 库与头文件合集,面向需要在 VS2015 中搭建 gRPC 服务端和客户端的 C++ 开发者,可省去从源码编译 gRPC 及依赖组件的繁琐环节。包体共 458 个文件、34.55MB,以 346 个头文件和 51 个 lib 库文件为主,另有 cmake 配置、proto 定义、exe 工具、DLL 及少量源文件,分别用于构建、接口描述和运行时支撑。包内提供的 protobuf 生成示例代码,便于理解 Stub、Channel、Server 等核心组件用法。基于 HTTP/2 多路复用和 protobuf 二进制编码,可在 VS2015 下直接开发高吞吐、低延迟的分布式服务,支持 TLS 安全通道,适合微服务、物联网或数据同步场景。目前已有 620 人学习/下载,适合快速上手 gRPC 并绕开编译配置坑位的开发者和学习者。 很多刚接触gRPC的兄弟,经常在grpc库和头文件这一步卡住。明明代码照着文档写得一模一样,一编译就是fatal error: grpcpp/grpcpp.h: No such file or directory,或者一堆undefined reference。说实话,这跟代码水平没关系,纯粹是gRPC这套东西的头文件和库太“碎”了——它依赖protobuf、absl、c-ares、re2、zlib一堆底层库,每个库又有自己的头文件目录,而且版本之间还互相锁死。我这几年前前后后在十几个项目里折腾gRPC,从源码编译到vcpkg、conan都踩过,踩得多了就攒了不少经验,今天正好整理出来,给正准备上手或者已经被编译折磨得想摔键盘的朋友做个参考。这篇文章主要解决三件事:第一,怎么选一个相对稳的gRPC版本,而不是无脑追最新;第二,怎么把grpc的头文件路径和库文件正确配进工程里,CMake、Makefile、直接命令行编译我都会讲到;第三,把最常见的几个编译链接报错整理成排查手册,你对照着改就行。
1. 为什么gRPC的头文件和库是最容易翻车的环节
1.1 它不是一个“独立库”,而是一整套生态
很多人第一次用gRPC的时候,以为它像OpenSSL那样下载一个包、加个include路径、链接一个lib就完事了。实际上gRPC的C++实现是一个典型的“库的库”,它的核心库grpc++依赖Protobuf做序列化,依赖absl做基础工具库,依赖c-ares做DNS解析,依赖re2做正则匹配,还有OpenSSL、zlib等等。这些依赖各自都有独立的头文件目录和独立的.so/.a文件。
这就带来一个直接后果:头文件不是只有gRPC自己的,还有一堆间接头文件。你在代码里写了#include <grpcpp/grpcpp.h>,编译器不光要能找到grpcpp/grpcpp.h本身,还要找到它里面引用的grpc/grpc.h、grpc/impl/codegen/...,以及它背后Protobuf的google/protobuf/message.h。任何一个目录没加进去,编译就挂。
打个比方,这就像你请了一个大厨(gRPC主库),结果这个大厨非要自带一整个后厨团队(依赖库)才肯开工。你光把大厨叫来没用,整个团队都得安置好。很多人上网搜解决方案,看到别人的头文件路径配置能用,自己照抄却不行,多半就是因为版本不同,依赖的具体路径不一样。
1.2 版本选型:最新不等于最稳,要看你的项目“体质”
“最新grpc库和头文件”这个概念本身就有坑。我见过有人一上来就clone master分支,结果编译都过不去,因为master上的代码可能依赖了还没release的protobuf版本。我的建议是,优先选release版本,绝对不要碰master。
具体怎么选,给你一个我用了很久的参考逻辑:
- 如果是新项目,没有任何历史包袱,选最新的稳定release版本,但要注意它要求的protobuf最低版本。比如gRPC 1.60要求protobuf 3.21以上,你就不能拿一个系统自带的旧protobuf去配。
- 如果项目里已经有固定版本的protobuf,那就去查这个protobuf版本对应的gRPC版本兼容性。gRPC官方在
grpc-releases分支和CHANGELOG里都有说明,别拍脑袋决定。 - 如果是在老系统上部署(比如CentOS 7这种),建议选稍微老一点的gRPC版本,因为新版gRPC可能要求更高的glibc和编译器版本,老系统的默认gcc压根不够格。
还有一个经常被忽略的点:gRPC的版本和头文件是强绑定的。你用什么版本的库编译出来的libgrpc++.so,就必须用什么版本的头文件去编译你的代码。混用版本是C++项目里最常见的“警告一堆但能编译过,运行起来偶尔崩”的神秘bug来源,这类问题最难排查,因为不是必现的,往往压测或者跑到特定逻辑才爆。
2. 获取最新gRPC库的几种主流方式
2.1 源码编译:最稳、也最费时间的一条路
如果你需要定制化(比如修改底层网络参数、裁剪部分模块),或者你用的操作系统比较小众、没有现成的二进制包,那源码编译是唯一靠谱的选择。你需要准备的东西有这些:git、cmake(3.13以上)、gcc/g++(支持C++14)、make、pkg-config,还有至少4GB的磁盘空间和比较稳的网络(因为要拉一堆submodule,网络不稳很容易中途失败)。
具体操作流程我整理如下:
# 1. 拉取源码,注意--recurse-submodules,这个千万别省 git clone --recurse-submodules -b v1.60.0 https://github.com/grpc/grpc.git cd grpc # 2. 创建build目录,out-of-source编译,避免污染源码 mkdir -p build && cd build # 3. 配置编译选项 cmake -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_INSTALL_PREFIX=/usr/local \ -DgRPC_INSTALL=ON \ -DgRPC_BUILD_TESTS=OFF \ .. # 4. 编译并安装,用nproc拿CPU核数,可以快不少 make -j$(nproc) sudo make install几个参数的解释,新手容易忽略:
-DgRPC_BUILD_TESTS=OFF:默认gRPC的测试代码挺多的,不关掉编译时间会变得非常长。我们这里只要库和头文件,不需要跑测试。-DgRPC_INSTALL=ON:让cmake帮我们把头文件、库文件、cmake配置文件一起装到CMAKE_INSTALL_PREFIX指定的目录,这一步直接决定了后面find_package能不能找到。-DCMAKE_INSTALL_PREFIX=/usr/local:安装路径。我习惯装在/usr/local,因为这样CMake的默认搜索路径就能直接找到,不用额外设置环境变量。如果你想装到自定义目录,后面就要注意CMAKE_PREFIX_PATH的设置。
源码编译这套流程我第一次跑的时候花了快40分钟,当时没关测试,机器还在跑别的东西。后来关了测试、用全部核数,大概10分钟出头就能搞定,新机器会更快。编译完成后,你可以检查一下/usr/local/include/grpcpp/grpcpp.h这个文件是否存在,存在就说明头文件装好了。如果这个文件都不存在,后面所有编译报错都不用看,先回头查安装步骤。
2.2 包管理器:vcpkg和apt的取舍
如果你不想折腾源码编译,包管理器是更快的路。我主要推荐两个,各有利弊。
vcpkg(跨平台,Windows/Linux/macOS都能用):
git clone https://github.com/microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh ./vcpkg install grpc装完之后,vcpkg会帮你把头文件和库都放在一个统一的目录里,然后通过CMAKE_TOOLCHAIN_FILE变量集成进工程。这个方案的优势是依赖关系全部自动处理,你不用自己操心absl、c-ares那些间接依赖。有坑的地方也要提前知道:它编译出来的库默认使用它自己拉下来的protobuf版本,如果你项目里还要跟自己的protobuf混用,反而麻烦。建议的做法是全工程统一用vcpkg管理依赖,不要一半vcpkg一半自己找。
apt(Ubuntu/Debian):
sudo apt install libgrpc++-dev protobuf-compiler-grpcapt装起来最快,但版本通常很老。我前阵子看了眼,Ubuntu 22.04默认源里的gRPC是1.30版本,跟当时最新版本差了好几个大版本。如果你的项目只是学习、测试,apt装装无所谓;如果是正经项目,我建议还是用源码编译或者vcpkg,别被apt的“省事”坑了版本。
还有一个值得提的是conan,在C++领域也越来越多项目用。不过conan的gRPC包有时候依赖项配置比较复杂,新手容易在profile设置上翻车。上手阶段,vcpkg比conan对新手友好得多,等你把CMake和依赖管理这套东西玩明白了,再去看conan会顺很多。
3. 头文件配置与工程集成实战
3.1 include路径和库链接:搞清楚这三件事就通透了
很多人在网上搜“grpc 头文件 配置”,搜到的答案五花八门,什么设置环境变量、改.vscode配置、在VS2015里加路径……看得人头晕。其实不管什么IDE、什么构建系统,底层就三件事:告诉编译器头文件在哪、告诉链接器库文件在哪、告诉运行时去哪找动态库。
头文件路径方面,gRPC安装之后你的include目录里大概会长这样:
/usr/local/include/ ├── grpc/ ├── grpcpp/ ├── google/protobuf/ # 如果编译时也安装了protobuf └── absl/ # 如果编译时安装了absl对应到CMake里,最朴素的写法是:
include_directories(/usr/local/include) link_directories(/usr/local/lib)但我强烈不建议长期这么干,因为link_directories是有坑的,它会全局影响后面所有target的链接搜索路径,项目一大容易出莫名其妙的问题。更规范的做法是用find_package,让CMake自己去找:
find_package(gRPC CONFIG REQUIRED) find_package(Protobuf REQUIRED)只要gRPC是正常安装的,CMake会自动帮你在gRPC_INCLUDE_DIR和Protobuf_INCLUDE_DIR里填好正确的头文件路径,不需要你手动写死。
库链接方面,你要链接的库至少包含这几个:
grpc++:C++封装的gRPC核心库grpc:C语言底层库protobuf:序列化库gpr:一些基础工具函数,老版本需要显式链接,新版一般自动带上了
对应的CMake写法是:
target_link_libraries(your_target grpc++ grpc protobuf )3.2 一个可以直接抄的CMakeLists.txt示例
光说理论容易飘,我给你一个可以直接抄的CMakeLists.txt。这个例子假设你的项目结构是:
my_grpc_demo/ ├── CMakeLists.txt ├── proto/ │ └── hello.proto └── src/ └── main.cppcmake_minimum_required(VERSION 3.13) project(my_grpc_demo CXX) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 如果gRPC不是装在标准路径,取消注释下面这行,改成你自己的安装前缀 # set(CMAKE_PREFIX_PATH "/path/to/grpc/install") find_package(gRPC CONFIG REQUIRED) find_package(Protobuf REQUIRED) # 定义proto生成的方式 set(PROTO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/proto) set(PROTO_FILE ${PROTO_PATH}/hello.proto) set(GENERATED_SRC ${CMAKE_CURRENT_BINARY_DIR}/hello.pb.cc ${CMAKE_CURRENT_BINARY_DIR}/hello.grpc.pb.cc) add_custom_command( OUTPUT ${GENERATED_SRC} COMMAND protoc --cpp_out=${CMAKE_CURRENT_BINARY_DIR} --grpc_out=${CMAKE_CURRENT_BINARY_DIR} --plugin=protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin> -I ${PROTO_PATH} ${PROTO_FILE} DEPENDS ${PROTO_FILE} ) add_custom_target(generate_proto ALL DEPENDS ${GENERATED_SRC}) add_executable(my_grpc_demo src/main.cpp ${GENERATED_SRC}) add_dependencies(my_grpc_demo generate_proto) target_include_directories(my_grpc_demo PRIVATE ${CMAKE_CURRENT_BINARY_DIR} # 生成的proto头文件在这里 ) target_link_libraries(my_grpc_demo PRIVATE gRPC::grpc++ protobuf::libprotobuf )核心设计我解释一下:add_custom_command负责调用protoc生成四个文件,--plugin=protoc-gen-grpc=$<TARGET_FILE:gRPC::grpc_cpp_plugin>这个写法很关键,它直接拿到grpc_cpp_plugin的绝对路径,不管你库装在哪都能找到。然后target_include_directories必须加上${CMAKE_CURRENT_BINARY_DIR},因为生成的hello.pb.h在这个目录里。
这里有一个新手经常栽的地方:hello.pb.h和hello.grpc.pb.h生成在当前二进制目录,不是源码目录。如果你没用target_include_directories把${CMAKE_CURRENT_BINARY_DIR}加进去,编译的时候就会报hello.pb.h: No such file or directory。这个错误我见过太多次了,几乎每隔一段时间就有人在群里问一次,每次排查到最后都是这个问题。
4. 实操过程:从零到一跑通一个gRPC服务
4.1 编写proto文件与生成代码
不管你是做服务器还是客户端,gRPC的第一步都是写proto文件。这是定义接口和数据结构的文件,格式长这样:
syntax = "proto3"; package helloworld; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) {} } message HelloRequest { string name = 1; } message HelloReply { string message = 1; }然后用protoc生成C++代码:
protoc -I ./proto --cpp_out=./generated --grpc_out=./generated \ --plugin=protoc-gen-grpc=$(which grpc_cpp_plugin) ./proto/hello.proto生成之后你会得到四个文件:hello.pb.h、hello.pb.cc、hello.grpc.pb.h、hello.grpc.pb.cc。前两个是protobuf序列化相关的类,后两个是gRPC服务接口相关的类。注意:grpc_cpp_plugin的路径一定要对,它通常在/usr/local/bin/grpc_cpp_plugin。用vcpkg的话,路径在vcpkg的tools/grpc目录里。如果你命令行执行which grpc_cpp_plugin什么都查不到,大概率是安装时候的bin目录没加进PATH。
4.2 编写服务端与客户端代码,编译运行验证
服务端代码我写得尽量精简,让你看清楚核心流程:
#include <grpcpp/grpcpp.h> #include "hello.grpc.pb.h" class GreeterServiceImpl final : public helloworld::Greeter::Service { grpc::Status SayHello(grpc::ServerContext* context, const helloworld::HelloRequest* request, helloworld::HelloReply* reply) override { std::string prefix("Hello "); reply->set_message(prefix + request->name()); return grpc::Status::OK; } }; int main(int argc, char** argv) { std::string server_address("0.0.0.0:50051"); GreeterServiceImpl service; grpc::ServerBuilder builder; builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); builder.RegisterService(&service); std::unique_ptr<grpc::Server> server(builder.BuildAndStart()); std::cout << "Server listening on " << server_address << std::endl; server->Wait(); return 0; }客户端代码:
#include <grpcpp/grpcpp.h> #include "hello.grpc.pb.h" int main(int argc, char** argv) { auto channel = grpc::CreateChannel( "localhost:50051", grpc::InsecureChannelCredentials()); auto stub = helloworld::Greeter::NewStub(channel); helloworld::HelloRequest request; request.set_name("grpc"); helloworld::HelloReply reply; grpc::ClientContext context; grpc::Status status = stub->SayHello(&context, request, &reply); if (status.ok()) { std::cout << "Received: " << reply.message() << std::endl; } else { std::cout << "RPC failed: " << status.error_message() << std::endl; } return 0; }编译运行:
mkdir build && cd build cmake .. make -j$(nproc) # 终端A运行服务端 ./my_grpc_demo # 终端B运行客户端 ./my_grpc_client如果一切正常,客户端应该打印出Received: Hello grpc。到这步,你的gRPC环境就算是真正跑通了。我第一次在CentOS上跑通这个流程的时候,光环境就折腾了两天,现在想想大部分时间都花在头文件和库的路径上,代码本身反而很简单。所以如果你环境还没通,别怀疑自己写代码的能力,先把库和头文件理顺。
5. 常见问题与排查技巧实录
5.1 头文件找不到:先分清是哪一层找不到
“fatal error: grpcpp/grpcpp.h: No such file or directory”这类报错,我建议你按下面这个顺序排查:
| 症状 | 可能原因 | 快速验证方法 |
|---|---|---|
找不到grpcpp/grpcpp.h | gRPC头文件没装,或include路径没加 | ls /usr/local/include/grpcpp/grpcpp.h |
能找到grpcpp/grpcpp.h,但找不到google/protobuf/message.h | protobuf头文件路径缺失 | 在CMake里单独find_package(Protobuf)并加上include路径 |
| VSCode里能编译过但编辑器画红线 | VSCode的C/C++插件用了自己的一套includePath | 在.vscode/c_cpp_properties.json里配置compileCommands或手动加includePath |
hello.pb.h找不到 | 生成的代码路径没加到include | 检查target_include_directories是否包含${CMAKE_CURRENT_BINARY_DIR} |
这里面最容易被误导的是VSCode的“假报错”——CMake编译完全OK,但编辑器里全是红色波浪线。这是因为VSCode的IntelliSense和CMake的编译配置是两套体系,它默认没有加载你的编译命令。解决办法是用CMake Tools插件,然后在c_cpp_properties.json里把"configurationProvider": "ms-vscode.cmake-tools"配置好,让插件自动读取compile_commands.json。如果你用的是Visual Studio而不是VSCode,道理一样,配置好include目录和库目录就行,只是入口在项目属性页里。
5.2 链接阶段报错:undefined reference的真相
编译通过了,但链接时报一堆undefined reference to 'grpc::...',这个问题的本质是链接器找不到对应符号,或者找到了但符号不一致。符号不一致通常是版本混用导致的,比如你用1.60的头文件去链接1.50的库,C++名字修饰规则变了,自然就找不到。
几个实用的排查命令:
# 查看动态库导出了哪些符号,确认grpc::CreateChannel符号在不在 nm -D /usr/local/lib/libgrpc++.so | grep "grpc::CreateChannel" # 查看库文件是32位还是64位 file /usr/local/lib/libgrpc.a # 查看可执行文件链接了哪些库 ldd your_app对于“如何查看.a库是32位还是64位”这个问题,file命令最直接,它会输出类似ELF 64-bit LSB relocatable, x86-64的信息。必要时还可以用objdump -f libxxx.a看文件头。很多时候你从网上下了一个旧库,或者手滑装了32位版本,链接阶段就会报一堆奇奇怪怪的错误,拿file一验就能定位。
5.3 运行时崩溃或连接失败:检查动态库路径
编译链接都过了,结果一运行报error while loading shared libraries: libgrpc++.so.1: cannot open shared object file,这是运行时动态库搜索路径的问题。程序运行时用的库路径和编译时的库路径不是一回事,这一点很多人会忽略。
解决方法:
# 方式一:临时设置LD_LIBRARY_PATH export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH # 方式二:用ldconfig把路径注册进系统缓存(推荐) sudo sh -c "echo /usr/local/lib > /etc/ld.so.conf.d/grpc.conf" sudo ldconfig # 验证 ldconfig -p | grep grpc如果你是在Windows上用vcpkg,那动态库的路径问题通常表现为grpc.dll找不到,把vcpkg的bin目录加到系统PATH里就行。Windows下还有个常见问题:Debug版本和Release版本的库混用,也会导致运行时行为异常,所以用vcpkg安装的时候要注意--triplet是x64-windows还是x64-windows-debug。
5.4 一个隐藏很深的坑:protoc版本与protobuf版本不匹配
这个坑我必须要单独说。gRPC生成代码依赖protoc,而protoc是和protobuf绑定的。如果你系统里的protoc是3.6版本,但你链接的protobuf库是3.21版本,生成的代码极有可能编译不过,或者编译过了运行时给你抛一个GPB_WARN_INVALID_UTF8之类的诡异错误。
应对思路:使用gRPC官方推荐的protoc版本。源码编译gRPC时,make install会把它自己拉下来的protobuf装好,这时你用的protoc就是配套版本。如果你用apt装protoc,系统的protoc版本可能和vcpkg装的gRPC不一致,就要小心了。最稳的做法是:哪个gRPC装出来的protoc,就用哪个protoc来生成代码。我们在CMake里用$<TARGET_FILE:gRPC::grpc_cpp_plugin>拿插件路径,其实protoc本身也可以从gRPC的安装目录里指定,别直接用系统的/usr/bin/protoc。
6. 写在最后:关于版本选择的一些个人体会
每次看到有人追求“最新grpc库和头文件”,我都想说一句:新版本确实会带来性能优化和新特性,但对大多数项目来说,稳定可复现比版本数字重要得多。我现在的习惯是,一个项目一旦确定了gRPC版本,就把它写进依赖锁定文件里(比如vcpkg的vcpkg.json的version约束、Conan的lockfile),绝不随手升级。真要升级,也是单独拉一个分支,把编译、测试、集成全套跑完再合并,绝不直接在主干上升级依赖。
最后再分享一个小技巧:grpc_cpp_plugin这个插件在生成代码时非常关键,但它不在PATH里是常态。你可以在CMake里用$<TARGET_FILE:gRPC::grpc_cpp_plugin>拿它的绝对路径,这样不管库装在哪,CMake都能自己找到。这个写法在上面那个CMakeLists.txt里已经用了,你直接抄就行。祝各位都能一次编译通过,少踩我当年踩过的坑。
本文还有配套的精品资源,点击获取