grpc与protobuf最佳实践:编译、安装与使用

2020/03/02 rpc

此文首发于我的Jekyll博客:zhang0peter的个人博客


最近要用到grpc,因此写一篇关于 grpc 静态编译和使用的文章

下载

在 Linux/ubuntu 上安装需要的软件:

apt install make gcc g++ cmake build-essential autoconf libtool pkg-config git unzip

下载库:

#记得改为最新版本的 grpc
git clone -b v1.27.3 https://github.com/grpc/grpc
cd grpc
git submodule update --init --recursive

安装 protobuf-protoc

google的grpc使用的protobuf作为序列化数据的格式。

推荐编译安装:

cd third_party/protobuf 
./autogen.sh
./configure
make -j 2

警告:如果你想静态编译程序,不要安装!!

sudo make install
sudo ldconfig # refresh shared library cache.

或者直接从网上下载最新版本的protobuf:

#! /bin/bash
# Make sure you grab the latest version
curl -OL https://github.com/google/protobuf/releases/download/v3.11.4/protoc-3.11.4-linux-x86_64.zip

# Unzip
unzip protoc-3.11.4-linux-x86_64.zip -d protoc3
# Move protoc to /usr/local/bin/
sudo cp -r protoc3/bin/* /usr/local/bin/
# Move protoc3/include to /usr/local/include/
sudo cp -r protoc3/include/* /usr/local/include/
# Optional: change owner
sudo chown $USER /usr/local/bin/protoc
sudo chown -R $USER /usr/local/include/google

sudo ldconfig # refresh shared library cache.
-> # protoc --version
libprotoc 3.11.4

不推荐直接安装protoc,库中的protoc版本比较旧:

apt install libprotobuf-dev protobuf-compiler

-> # protoc --version
libprotoc 3.0.0
export CFLAGS='-Wno-implicit-fallthrough'
export CXXFLAGS='-Wno-expansion-to-defined -Wno-implicit-fallthrough'
cmake .
make -j 2
#make REQUIRE_CUSTOM_LIBRARIES_opt=1 static

注意:如果安装grpc到系统目录后,无法轻易卸载

警告:如果你想静态编译程序,不要安装!!

sudo make install

动态编译测试

警告:如果测试通不过,后续就不用做了,重装系统吧

如果没安装protoc或者grpc,自然无法编译,具体的静态编译过程见下面的示例

编译官方样例:

cd examples/cpp/helloworld/
make
# ./greeter_server 
Server listening on 0.0.0.0:50051
~/grpc/examples/cpp/helloworld$ ./greeter_client 
Greeter received: Hello world

自己编写 Protobuf 文件与服务器/客户端代码

examples.proto :

syntax = "proto3";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

服务器端代码 examples_server.cc:

#include <iostream>
#include <memory>
#include <string>

#include <grpc++/grpc++.h>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "examples.grpc.pb.h"
#endif

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

// Logic and data behind the server's behavior.
class GreeterServiceImpl final : public Greeter::Service
{
  Status SayHello(ServerContext *context, const HelloRequest *request,
                  HelloReply *reply) override
  {
    std::string prefix("Hello ");
    reply->set_message(prefix + request->name());
    return Status::OK;
  }
};

void RunServer()
{
  std::string server_address("0.0.0.0:50051");
  GreeterServiceImpl service;

  ServerBuilder builder;
  // Listen on the given address without any authentication mechanism.
  builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
  // Register "service" as the instance through which we'll communicate with
  // clients. In this case it corresponds to an *synchronous* service.
  builder.RegisterService(&service);
  // Finally assemble the server.
  std::unique_ptr<Server> server(builder.BuildAndStart());
  std::cout << "Server listening on " << server_address << std::endl;

  // Wait for the server to shutdown. Note that some other thread must be
  // responsible for shutting down the server for this call to ever return.
  server->Wait();
}

int main(int argc, char **argv)
{
  RunServer();

  return 0;
}

客户端代码 examples_client.cc



#include <iostream>
#include <memory>
#include <string>

#include <grpc++/grpc++.h>

#ifdef BAZEL_BUILD
#include "examples/protos/helloworld.grpc.pb.h"
#else
#include "examples.grpc.pb.h"
#endif

using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using helloworld::Greeter;
using helloworld::HelloReply;
using helloworld::HelloRequest;

class GreeterClient
{
public:
  GreeterClient(std::shared_ptr<Channel> channel)
      : stub_(Greeter::NewStub(channel)) {}

  // Assembles the client's payload, sends it and presents the response back
  // from the server.
  std::string SayHello(const std::string &user)
  {
    // Data we are sending to the server.
    HelloRequest request;
    request.set_name(user);

    // Container for the data we expect from the server.
    HelloReply reply;

    // Context for the client. It could be used to convey extra information to
    // the server and/or tweak certain RPC behaviors.
    ClientContext context;

    // The actual RPC.
    Status status = stub_->SayHello(&context, request, &reply);

    // Act upon its status.
    if (status.ok())
    {
      return reply.message();
    }
    else
    {
      std::cout << status.error_code() << ": " << status.error_message()
                << std::endl;
      return "RPC failed";
    }
  }

private:
  std::unique_ptr<Greeter::Stub> stub_;
};

int main(int argc, char **argv)
{
  // Instantiate the client. It requires a channel, out of which the actual RPCs
  // are created. This channel models a connection to an endpoint (in this case,
  // localhost at port 50051). We indicate that the channel isn't authenticated
  // (use of InsecureChannelCredentials()).
  GreeterClient greeter(grpc::CreateChannel(
      "localhost:50051", grpc::InsecureChannelCredentials()));
  std::string user("world");
  std::string reply = greeter.SayHello(user);
  std::cout << "Greeter received: " << reply << std::endl;

  return 0;
}

静态编译链接程序

静态链接的成果是不依赖外部库,可以放到其他机器上直接运行。

编写Makefile,记得把GRPC_PATHPROTOBUF_PATH换成你的目录。

>subdir = ./

SOURCES = $(wildcard $(subdir)*.cc)
SRCOBJS = $(patsubst %.cc,%.o,$(SOURCES))
GXX = g++

GXXFLAGS = -O0 -g2  \
		   -Wall  -std=c++11 -I./  \
		   -I$(PROTOBUF_PATH) -I$(GRPC_PATH)/include

GRPC_PATH= /home/zhang/grpc
PROTOBUF_PATH= /home/zhang/grpc/third_party/protobuf/src

BUILDFLAGS = -static-libgcc -static-libstdc++ -std=c++11
DLIBS = -pthread -ldl

SLIBS = $(PROTOBUF_PATH)/.libs/libprotobuf.a \
		$(GRPC_PATH)/libs/opt/libares.a \
        $(GRPC_PATH)/libs/opt/libboringssl.a \
        $(GRPC_PATH)/libs/opt/libgpr.a \
        $(GRPC_PATH)/libs/opt/libgrpc.a \
        $(GRPC_PATH)/libs/opt/libgrpc++.a \
        $(GRPC_PATH)/libs/opt/libgrpc++_core_stats.a \
        $(GRPC_PATH)/libs/opt/libgrpc++_error_details.a \
        $(GRPC_PATH)/libs/opt/libgrpc_plugin_support.a \
        $(GRPC_PATH)/libs/opt/libgrpc_unsecure.a \
        $(GRPC_PATH)/libs/opt/libgrpc++_unsecure.a \
        $(GRPC_PATH)/libs/opt/libz.a \
        -Wl,--no-as-needed $(GRPC_PATH)/libs/opt/libgrpc++_reflection.a -Wl,--as-needed

%.grpc.pb.cc: %.proto
	$(PROTOBUF_PATH)/protoc --grpc_out=./ --plugin=protoc-gen-grpc=$(GRPC_PATH)/bins/opt/grpc_cpp_plugin $^

%.pb.cc: %.proto
	$(PROTOBUF_PATH)/protoc --cpp_out=./ $^

%.pb.o: %.pb.cc
	$(GXX) $(GXXFLAGS) -c $^ -o $@

%.o:%.cc
	$(GXX) $(GXXFLAGS) -c $< -o $@

%.o: %.cpp
	$(GXX) $(GXXFLAGS) -c $^ -o $@

all: client server

client: examples.pb.o examples.grpc.pb.o  examples_client.o
	$(GXX) $(BUILDFLAGS) $^ -o $@ $(SLIBS) $(DLIBS)

server: examples.pb.o examples.grpc.pb.o  examples_server.o
	$(GXX) $(BUILDFLAGS) $^ -o $@ $(SLIBS) $(DLIBS)

clean:
	rm *.o *.pb.cc *.pb.h server client

编译完成后程序很大,但不依赖grpc.so等库

-> % ldd server 
	linux-vdso.so.1 (0x00007ffdb2154000)
	libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007ff6f766f000)
	libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007ff6f7450000)
	libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff6f705f000)
	/lib64/ld-linux-x86-64.so.2 (0x00007ff6f8104000)

后记

关于grpc的其他文章如下: