gRPC 是 Google 开发的一个高性能、开源和通用的 RPC 框架,主要面向移动应用开发并基于 HTTP/2 协议标准而设计,基 于 Protobuf 3.0(Protocol Buffers) 序列化协议。目前提供 C、Java 和 Go 等多种语言版本,分别是:grpc、grpc-java、grpc-go。其中 C 版本支持 C、C++、Node.js、Python、Ruby、Objective-C、PHP 和 C# 。

这篇文章主要讲述如何在 Windows 系统上编译 gRPC 及如何在 C++ 语言中(同步)使用 gRPC 。

因为 gRPC 基 于Protocol Buffers 序列化协议,在阅读这篇文章之前,你需要对 Protocol Buffers 有一定的了解,推荐阅读这篇文章

编译

自 gRPC v1.6 版本以后,源码中不再提供 Visual Studio 工程文件,需要使用 CMake 工具编译。

在 Linux、macOS 平台的安装方法,及更多使用源码在 Windows 下编译的方法可以参考官方提供的 INSTALL 文件。

下面讲述如果使用 CMake 工具,编辑出 gRPC 的 Visual Studio 工程文件。

注意:因为 gRPC 的 CMake 文件中提到了下面的一段话:

set(gRPC_INSTALL ${gRPC_INSTALL_default} CACHE BOOL
"Generate installation target: gRPC_ZLIB_PROVIDER, gRPC_CARES_PROVIDER, gRPC_SSL_PROVIDER and gRPC_PROTOBUF_PROVIDER must all be \"package\"")
gRPC_INSTALL will be forced to FALSE because gRPC_CARES_PROVIDER is "module"

这段话的解释为:如果以源码方式提供选择 module,如果以库文件和头文件的方式提供选择 package,所以本文章中先静态编译了 zlib、c-ares、OpenSSL 及 protobuf ,再编译 gRPC 。

构建需求

  • CMake ,在官方网站下载最新版本的 CMake 安装包进行安装;
  • Perl,推荐使用 ActiveState Perl ,编译 OpenSSL 时会用到;
  • NASM,在官方网站下载安装,编译 OpenSSL 时会用到;
  • 安装 Git ,并将 git.exe 所在的目录添加到环境变量 PATH 中;
  • gRPC 源码,在 GitHub 下载。

编译 zlib

官方网站下载 zlib 源码,并解压。在命令行工具中依次执行下列命令(也可使用 CMake GUI 工具):

$ cd zlib-1.2.11
$ md build & cd build
$ cmake -G "Visual Studio 14 2015" ..

执行完成后打开 zlib-1.2.11/build 目录下的 zlib.sln 文件(项目属性 -> 运行库改为/MT ,也可在运行 CMake 命令时修改),编译子zlibstatic项目即可,编译成功后生成静态库文件zlibstatic.lib

编译 OpenSSL

本文中使用的 OpenSSL 版本是 OpenSSL_1_1_0OpenSSL_1_1_0 以下的版本编译方法请参看:http://www.pressc.cn/?p=812

官方网站下载 OpenSSL 源码,并解压。打开Visual Studio Command Prompt (2015) ,依次输入下列命令:

$ cd <openssl-OpenSSL_1_x_x sorce dir>
$ perl Configure VC-WIN32 no-shared enable-capieng --prefix="E:\thirdparty\openssl" --openssldir="E:\thirdparty\ssl"
$ nmake
$ nmake test
$ nmake install
  • Configure命令对安装进行配置,其中VC-WIN32是指定构建的平台,可选的参数有(选择其中之一):VC-WIN32 | VC-WIN64A | VC-WIN64I | VC-CE ,如果想要构建debug版本使用debug-VC-WIN32参数即可。--prefix参数指定库文件和头文件(libincludebin)的安装位置,如果不指定将会默认安装到%ProgramFiles(86)%\OpenSSL目录。--openssldir参数指定OpenSSL的安装路径,如果不指定将会默认安装到%CommonProgramFiles(86)%\SSL目录。
  • no-shared 参数表示构建静态库。
  • enable-capieng 参数表示在 Windows 平台启用 Microsoft CAPI 引擎。

更多参数及描述参看:Compilation and Installation

构建完成后在lib目录下生成libssl.lib 和libcrypto.lib 。在环境变量中添加一项 OPENSSL_ROOT_DIR ,值为E:\thirdparty\openssl ,根据实际情况更改。

编译 c-ares

GitHub 下载 c-ares 源码并解压,使用 CMake 用上述方法(步骤同构建 zlib )构建出 Visual Studio 工程文件。如果要生成静态工程,在运行 CMake 命令时指定 CARES_STATIC 参数即可。完成后打开 c-ares-cares-1_13_0/build 目录下的 c-ares.sln 文件(项目属性 -> 运行库改为/MT ,也可在运行 CMake 命令时修改),编译成功后生成静态库文件cares.libc-ares-config.cmake CMakeFiles\Export\lib\cmake\c-ares\c-ares-targets.cmake

将生成的 cares.lib*.exe 和 头文件整理到一个目录下,我这里统一整理到了 E:\thirdparty\c-ares 目录下,将 c-ares-config.cmakec-ares-targets.cmake 也复制到上述目录。整理后的目录格式如下:

├── bin
|   └── *.exe
├── include
|   ├── ares_build.h
|   ├── ares.h
|   └── ...
├── lib
|   └── cares.lib
├── c-ares-config.cmake
└── c-ares-targets.cmake

并将 c-ares-config.cmake 文件第6行修改为(根据 include 文件的实际位置修改。):

get_filename_component(PACKAGE_PREFIX_DIR "${CMAKE_CURRENT_LIST_DIR}" ABSOLUTE)

c-ares-targets.cmake 文件的44 - 48行修改为(根据实际情况修改):

# Compute the installation prefix relative to this file.
get_filename_component(_IMPORT_PREFIX "E:/thirdparty/c-ares" ABSOLUTE)
#get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
#get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)
#get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH)

为什么要执行上述过程?

在 gRPC 的 CMakeLists.txt 文件中,c-aresfind_package 模式为 CONFIGfind_package(c-ares CONFIG) ),关于 find_package() 模式的解释如下:

find_package()包括module模式和config模式。
在module模式下,CMake搜索所有名为Find<package>.cmake的文件,这些文件的路径由变量由安装CMake时指定的CMAKE_MODULE_PATH变量指定。如果查找到了该文件,它会被CMake读取并被处理。如果没有找到文件,则进入config模式。

Config 模式,继续搜索<Name>config.cmake 或<low-case-name>config.cmake文件,这两个文件是安装库自动安装的。

编译 Protocol Buffer

下载 Protocol Buffers 源代码(下载Source Code protobuf-3.4.0.zip)并按 README文件 中的方法进行安装,这里选择创建一个 Visual Studio 解决方案文件,命令如下:

$ cd C:\Path\to\protobuf\cmake
$ mkdir build & cd build
$ mkdir solution & cd solution
$ cmake -G "Visual Studio 14 2015" -DCMAKE_INSTALL_PREFIX=../../../../install -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_WITH_ZLIB=ON -DZLIB_INCLUDE_DIR=E:/thirdparty/zlib/include -DZLIB_LIBRARY=E:/thirdparty/zlib/lib/zlibstatic.lib ../..

编译 gRPC

下载 gflags,放到 grpc-1.6.0/third_party/gflags 文件夹下。

下载 benchmark ,放到 grpc-1.6.0/third_party/benchmark 文件夹下。

在命令行工具中依次执行下列命令(参数太多推荐使用 CMake GUI 工具):

$ cd grpc-1.6.0
$ md .build & cd .build
$ cmake -G "Visual Studio 14 2015" $(some params) ..

可以使用 cmake . -LH 命令查看配置,也可以查看 CMakeCache.txt 文件,部分 CMake 参数如下:

//Link with static msvc runtime libraries
gRPC_MSVC_STATIC_RUNTIME:BOOL=ON

//Algorithm for searching protobuf package
gRPC_PROTOBUF_PACKAGE_TYPE:STRING=MODULE
//Provider of protobuf library
gRPC_PROTOBUF_PROVIDER:STRING=package
Protobuf_INCLUDE_DIR:PATH=E:/thirdparty/protobuf/include
Protobuf_LIBRARY_RELEASE:FILEPATH=E:/thirdparty/protobuf/lib/libprotobuf.lib
Protobuf_LITE_LIBRARY_RELEASE:FILEPATH=E:/thirdparty/protobuf/lib/libprotobuf-lite.lib
//The Google Protocol Buffers Compiler
Protobuf_PROTOC_EXECUTABLE:FILEPATH=E:/thirdparty/protobuf/bin/protoc.exe
Protobuf_PROTOC_LIBRARY_RELEASE:FILEPATH=E:/thirdparty/protobuf/lib/libprotoc.lib

//Provider of zlib library
gRPC_ZLIB_PROVIDER:STRING=package
ZLIB_INCLUDE_DIR:PATH=E:/thirdparty/zlib/include
ZLIB_LIBRARY:FILEPATH=E:/thirdparty/zlib/lib/zlibstatic.lib

gRPC_BENCHMARK_PROVIDER:STRING=module
benchmark_BINARY_DIR:STATIC=E:/thirdparty_source/grpc-1.6.0/.build/third_party/benchmark
benchmark_SOURCE_DIR:STATIC=E:/thirdparty_source/grpc-1.6.0/third_party/benchmark

//Provider of c-ares library
gRPC_CARES_PROVIDER:STRING=package
//The directory containing a CMake configuration file for c-ares.
c-ares_DIR:PATH=E:/thirdparty/c-ares

//Provider of gflags library
gRPC_GFLAGS_PROVIDER:STRING=module
//Value Computed by CMake
gflags_BINARY_DIR:STATIC=E:/thirdparty_source/grpc-1.6.0/.build/third_party/gflags
//Value Computed by CMake
gflags_SOURCE_DIR:STATIC=E:/thirdparty_source/grpc-1.6.0/third_party/gflags

//Provider of ssl library
gRPC_SSL_PROVIDER:STRING=package
OPENSSL_INCLUDE_DIR:PATH=E:/thirdparty/openssl/include

构建完成后,打开.build/grpc.sln,并编译。编译成功后生成的库文件如下:

gpr.lib
grpc.lib
grpc_cronet.lib
grpc_plugin_support.lib
grpc_unsecure.lib
grpc++.lib
grpc++_cronet.lib
grpc++_error_details.lib
grpc++_reflection.lib
grpc++_unsecure.lib

使用

接下来将演示如何使用 C++ 程序通过 gRPC 框架实现远程调用。你将学到:

  • .proto 文件中定义RPC服务。
  • 使用 protocol buffer 编译器生成服务端和客户端代码。
  • 使用 C++ gRPC API 写一个简单的 C/S 服务。

定义 gRPC 服务

首先使用 protocol buffer 定义一个 gRPC service 以及的 request 和 response 方法,可以参考  examples/protos/route_guide.proto 是如何完整定义服务的。

定义一个服务,你需要在 .proto 文件中指定一个 service 名称:

service RouteGuide {
   ...
}

然后你可以在 service 中定义  rpc 方法,并制定它的 request 和 response 类型。在 gRPC 中你可以定义四种类型的服务方法:

  • simple RPC - 简单 RPC ,客户端使用 stub 发送一个请求到服务端并等待响应返回,就像正常函数调用一样。

    // Obtains the feature at a given position.
    rpc GetFeature(Point) returns (Feature) {}
  • server-side streaming RPC - 服务端流式 RPC,客户端向服务端发送请求,并获取流来读取返回的消息序列。客户机从返回的流读取,直到不再有消息为止。在示例中可以看到,通过在 response 类型之前放置 stream 关键字来指定服务端流式方法。

    // Obtains the Features available within the given Rectangle.  Results are
    // streamed rather than returned at once (e.g. in a response message with a
    // repeated field), as the rectangle may cover a large area and contain a
    // huge number of features.
    rpc ListFeatures(Rectangle) returns (stream Feature) {}
  • client-side streaming RPC - 客户端流式 RPC ,客户端使用给定的流多次写入消息序列并将其发送到服务端。一旦客户端完成了消息的写入,它就等待服务端读取所有消息并返回其响应。通过在 request 类型之前放置 stream 关键字来指定客户端流式方法。

    // Accepts a stream of Points on a route being traversed, returning a
    // RouteSummary when traversal is completed.
    rpc RecordRoute(stream Point) returns (RouteSummary) {}
  • bidirectional streaming RPC - 双向流式 RPC ,双方都使用 read-write 流发送消息序列。两流独立运行,所以客户端和服务端可以用任何顺序读取和写入:例如,服务端可以在接收完所有客户消息之前写入响应信息,或者它也可以交替读取信息然后写入响应信息,或者一些其他组合的读和写。每条流中的消息顺序是保持不变的。通过在 requestresponse 类型之前放置 stream 关键字来指定这种类型的方法。

    // Accepts a stream of RouteNotes sent while a route is being traversed,
    // while receiving other RouteNotes (e.g. from other users).
    rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

.proto文件中还包含了我们服务方法中使用的所有请求和响应类型的 protocol buffer 消息类型定义 - 例如,下面定义了一个 Point 消息类型:

// Points are represented as latitude-longitude pairs in the E7 representation
// (degrees multiplied by 10**7 and rounded to the nearest integer).
// Latitudes should be in the range +/- 90 degrees and longitude should be in
// the range +/- 180 degrees (inclusive).
message Point {
  int32 latitude = 1;
  int32 longitude = 2;
}

生成客户端和服务端代码

接下来我们使用 .proto 服务定义生成 gRPC 客户端和服务端接口。我们使用 protocol buffer 编译器 protoc 和 gRPC C++ 插件。

在命令行提示符中运行下列命令:

$ "E:\thirdparty\protobuf\bin\protoc.exe" -I="E:\thirdparty_source\grpc-1.6.0\examples\protos" --grpc_out="E:\thirdparty_source\grpc-1.6.0\examples\protos" --plugin=protoc-gen-grpc="E:\thirdparty\grpc\bin\grpc_cpp_plugin.exe" "E:\thirdparty_source\grpc-1.6.0\examples\protos\helloworld.proto"
$ "E:\thirdparty\protobuf\bin\protoc.exe" -I="E:\thirdparty_source\grpc-1.6.0\examples\protos" --cpp_out="E:\thirdparty_source\grpc-1.6.0\examples\protos" "E:\thirdparty_source\grpc-1.6.0\examples\protos\helloworld.proto"

运行上面的命令可以在 E:\thirdparty_source\grpc-1.6.0\examples\protos 目录中生成下列文件:

  • route_guide.pb.h - 消息类的头文件
  • route_guide.pb.cc - 其中包含消息类的实现
  • route_guide.grpc.pb.h - 服务类的头文件
  • route_guide.grpc.pb.cc - 其中包含服务类的实现

这些文件包含:

  • 所有用来填充、序列化和检索我们的 requestresponse 消息类型的 protocol buffer 代码。
  • 一个名称为 RouteGuide 的类,包含:

    • 远程接口类型(也称为 stub) - RouteGuide 服务中定义的客户端调用方法。
    • 服务端需要实现的两个抽象接口,也是在 RouteGuide 服务定义的方法。

创建 Server

下面将介绍如何创建 RouteGuide Server 。创建 RouteGuide Server 需要做下面两部分工作:

  • 基于服务定义实现服务接口,也就是实现服务的功能。
  • 运行 gRPC 服务端侦听来自客户机的请求并返回服务响应。

你可以查看 RouteGuide 服务端的示例程序 route_guide/route_guide_server.cc

实现RouteGuide

首先创建一个 RouteGuideImpl 类实现前面生成的 RouteGuide::Service 接口。

class RouteGuideImpl final : public RouteGuide::Service {
...
}

在这里我们只实现 RouteGuide 的同步版本,它提供了默认的 gRPC 服务行为。当然你也可以使用 RouteGuide::AsyncService 实现一个异步接口,这能让你进一步定制服务端的线程行为。

我们需要在 RouteGuideImpl 类中实现所有的服务方法。让我们先从 simple 类型看起,GetFeature 函数从客户端获取 Point 信息,然后返回从数据库中查询到的相应的 Feature 信息。

Status GetFeature(ServerContext* context, const Point* point,
                    Feature* feature) override {
    feature->set_name(GetFeatureName(*point, feature_list_));
    feature->mutable_location()->CopyFrom(*point);
    return Status::OK;
 }

这个函数传递一个 RPC 的 context 对象,客户端传递过来的 Point 信息,和将要给客户端的 Feature 信息。在这个函数中我们填充相应的 Feature 信息,然后返回 OK 状态告诉 gRPC 我们已经结束 RPC 处理并且可以给客户端返回 Feature 信息。

现在我们看一下更复杂一点的 a streaming RPCListFeatures 是个 server-side streaming RPC ,所以我们需要发送多次 Feature 信息给客户端。

Status ListFeatures(ServerContext* context, const Rectangle* rectangle,
                    ServerWriter<Feature>* writer) override {
  auto lo = rectangle->lo();
  auto hi = rectangle->hi();
  long left = std::min(lo.longitude(), hi.longitude());
  long right = std::max(lo.longitude(), hi.longitude());
  long top = std::max(lo.latitude(), hi.latitude());
  long bottom = std::min(lo.latitude(), hi.latitude());
  for (const Feature& f : feature_list_) {
    if (f.location().longitude() >= left &&
        f.location().longitude() <= right &&
        f.location().latitude() >= bottom &&
        f.location().latitude() <= top) {
      writer->Write(f);
    }
  }
  return Status::OK;
}

simple RPC 不同的是,响应参数变成了 ServerWriter 对象。在这个函数中我们填充多个 Feature 对象(使用 ServerWriter::Write 函数)并返回给客户端,最后同样的使用 return Status::OK 完成响应。

再看一下 client-side streaming RPC 方法 RecordRoute ,他和 server-side streaming RPC 非常相似。不同的是使用 ServerReader 对象代替 request 对象,使用ServerReader::Read() 函数从客户端的请求中反复的读取数据,直到读取完所有的消息。服务端需要在每次调用后检查 Read() 的返回值。如果为 true ,流仍然是 good 并可以继续读取;如果为 false ,代表消息流已经结束。

while (reader->Read(&point)) {
  ...//process client input
}

最后,我们看一下 bidirectional streaming RPC RouteChat()

Status RouteChat(ServerContext* context,
                   ServerReaderWriter<RouteNote, RouteNote>* stream) override {
    std::vector<RouteNote> received_notes;
    RouteNote note;
    while (stream->Read(&note)) {
      for (const RouteNote& n : received_notes) {
        if (n.location().latitude() == note.location().latitude() &&
            n.location().longitude() == note.location().longitude()) {
          stream->Write(n);
        }
      }
      received_notes.push_back(note);
    }

    return Status::OK;
}

ServerReaderWriter 对象可用于读取和写入消息。这里的读写语法与 client-side streaming RPCserver-side streaming RPC 方法完全相同。

启动 Server

下面介绍如何启动一个 gRPC 服务器以便于客户端能够使用 RouteGuide 服务。

void RunServer(const std::string& db_path) {
  std::string server_address("0.0.0.0:50051");
  RouteGuideImpl service(db_path);

  ServerBuilder builder;
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  builder.RegisterService(&service);
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;
  server->Wait();
}

使用 ServerBuilder 启动gRPC 服务的步骤如下:

  1. 创建一个服务实现类的实例 RouteGuideImpl
  2. 创建一个 ServerBuilder 工厂类的实例;
  3. 使用 builder 对象的 AddListeningPort() 方法指定监听客户端请求的地址和端口‘
  4. 使用 builder 注册服务实现;
  5. 调用 builder 对象的 BuildAndStart() 方法创建并启动 RPC 服务器;
  6. 调用 Wait() 方法阻塞等待直到进程被关闭或 Shutdown() 方法被调用。

创建 Client

下面将介绍如何创建 RouteGuide C++ 客户端 。您可以看到完整的示例代码:route_guide/route_guide_client.cc

创建 stub

要调用服务方法,我们首先需要创建一个 stub

首先我们需要为 stub 创建一个 gRPC channel ,指定不使用 SSL 连接并指定服务端的地址和端口:

grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());
...
public:
 RouteGuideClient(std::shared_ptr<Channel> channel, const std::string& db)
     : stub_(RouteGuide::NewStub(channel)) {
   ...
 }

调用服务的方法

接下来将演示如何使用 blocking/synchronous 的方式调用服务方法: RPC 调用等待服务返回响应,并判断响应是否成功。

Simple RPC

调用 simple RPC 方法 GetFeature 几乎和调用本地方法一样简单。

Point point;
Feature feature;
point = MakePoint(409146138, -746188906);
GetOneFeature(point, &feature);

...

bool GetOneFeature(const Point& point, Feature* feature) {
    ClientContext context;
    Status status = stub_->GetFeature(&context, point, feature);
    ...
}

创建并填充请求 protocol buffer 对象(Point),并创建一个响应 protocol buffer 对象以等待服务端填充。我们也创造一个 ClientContext 对象 - 你可以通过这个对象设置 RPC 配置信息,例如 deadlines 。注意,在调用之间不能重用此对象。最后,我们在 stub 上调用该方法,并传递 contextrequestresponse 参数。如果方法返回 OK ,我们就可以从响应对象读取服务器的响应信息。

std::cout << "Found feature called " << feature->name()  << " at "
          << feature->location().latitude()/kCoordFactor_ << ", "
          << feature->location().longitude()/kCoordFactor_ << std::endl;

Streaming RPCs

streaming RPCs 的实现方式和服务端很相似。下面我们调用 server-side streaming 方法 ListFeatures 并等待服务端返回 Features(即多个 Feature)。

std::unique_ptr<ClientReader<Feature> > reader(
    stub_->ListFeatures(&context, rect));
while (reader->Read(&feature)) {
  std::cout << "Found feature called "
            << feature.name() << " at "
            << feature.location().latitude()/kCoordFactor_ << ", "
            << feature.location().longitude()/kCoordFactor_ << std::endl;
}
Status status = reader->Finish();

simple RPC 方法不同的是,这里传递了 contextrequest 参数,并返回一个 ClientReader 对象。客户端使用 ClientReaderRead() 方法反复的读取服务端的响应,直到没有没有更多的消息。Read() 返回返回 true 代表可以继续读取,如果返回 false 代表流已经结束。最后使用 Finish 方法完成调用并获取 RPC 响应状态。

client-side streaming 方法 RecordRoute 也很相似,在这个方法上传递 contextresponse 对象,并返回一个 ClientWriter 对象。

    std::unique_ptr<ClientWriter<Point> > writer(
        stub_->RecordRoute(&context, &stats));
    for (int i = 0; i < kPoints; i++) {
      const Feature& f = feature_list_[feature_distribution(generator)];
      std::cout << "Visiting point "
                << f.location().latitude()/kCoordFactor_ << ", "
                << f.location().longitude()/kCoordFactor_ << std::endl;
      if (!writer->Write(f.location())) {
        // Broken stream.
        break;
      }
      std::this_thread::sleep_for(std::chrono::milliseconds(
          delay_distribution(generator)));
    }
    writer->WritesDone();
    Status status = writer->Finish();
    if (status.IsOk()) {
      std::cout << "Finished trip with " << stats.point_count() << " points\n"
                << "Passed " << stats.feature_count() << " features\n"
                << "Travelled " << stats.distance() << " meters\n"
                << "It took " << stats.elapsed_time() << " seconds"
                << std::endl;
    } else {
      std::cout << "RecordRoute rpc failed." << std::endl;
    }

一旦我们使用 Write() 方法完成客户端的请求流写入,我们需要调用 WritesDone() 方法告诉 gRPC 已经结束流写入,然后调用 Finish() 方法等待调用完成并获取 RPC 状态。如果状态码为 OK 则代码响应成功。

最后是 bidirectional streaming RPC RouteChat() 方法,在这个方法上只传递 context 对象,并返回 ClientReaderWriter 对象。

std::shared_ptr<ClientReaderWriter<RouteNote, RouteNote> > stream(
    stub_->RouteChat(&context));

reading 和 writing 语法与 client-streamingserver-streaming 方法相同。

编译运行

将 gRPC 的 C++ example 代码拷贝到我们刚创建的项目中,需要用到的库文件如下:

libprotobuf.lib;gpr.lib;grpc.lib;grpc_cronet.lib;grpc_plugin_support.lib;grpc_unsecure.lib;grpc++.lib;grpc++_cronet.lib;grpc++_error_details.lib;grpc++_reflection.lib;grpc++_unsecure.lib;Ws2_32.lib;libcrypto.lib;libssl.lib;zlibstatic.lib;Crypt32.Lib;

如果编译时出现下列错误:

error C1189: #error :"Please compile grpc with _WIN32_WINNT of at least 0x600 (aka Windows Vista)"port_platform.h 59 Server_Cpp

只需在项目属性中的 Preprocessor Definitions 中添加 _WIN32_WINNT=0x600 即可。

深入阅读
gRPC 官方文档
gRPC 官方文档中文版 v1.0 (由多位网友在开源中国众包平台协作翻译完成)
深入了解 gRPC 协议

标签: protobuf, Protocol Buffer, OpenSSL, zlib, gRPC, RPC, c-ares

添加新评论