gRPC 基础

2023/05/25

1. 安装 gRPC

  1. 设置安装地址

    export MY_INSTALL_DIR=$HOME/.local
    mkdir -p $MY_INSTALL_DIR
  2. 安装编译依赖

    sudo apt install -y build-essential autoconf libtool pkg-config
  3. 下载代码并开始编译

    git clone --recurse-submodules -b v1.55.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
    
    cd grpc
    mkdir -p cmake/build
    pushd cmake/build
    cmake -DgRPC_INSTALL=ON \
          -DgRPC_BUILD_TESTS=OFF \
          -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
          ../..
    make -j8
    make install
    popd

2. Demo

  1. hello.proto

    protobuf 由 service 和 mssage 这两个基本部分组成,前者相当于函数,由服务端实现,客户端可以直接调用,后者则表示一种数据结构,在 cpp 中则是一个类,里面的就是类的成员变量,可以看到 message 中成员变量后面都有一个数字,在 protobuf 中就是用这个数字来表示这个变量,而不是采用这些变量名。

    syntax = "proto3";
    
    package hello;
    
    service Greeter{
        rpc SayHello(HelloRequest) returns (HelloReply){}
    }
    
    message HelloRequest{
        string name = 1;
    }
    
    message HelloReply{
        string message = 1;
    }
  2. server.cc

    server 里面实现对应的 service 需要定义一个类来继承自动生成的代码

    #include <string>
    #include <iostream>
    #include <memory>
    
    #include "absl/strings/str_format.h"
    #include "absl/flags/flag.h"
    #include "absl/flags/parse.h"
    
    #include <grpcpp/ext/proto_server_reflection_plugin.h>
    #include <grpcpp/grpcpp.h>
    #include <grpcpp/support/status.h>
    #include <grpcpp/health_check_service_interface.h>
    
    #include "grpcpp/health_check_service_interface.h"
    #include "grpcpp/security/server_credentials.h"
    #include "grpcpp/server_builder.h"
    #include "grpcpp/server_context.h"
    #include "hello.grpc.pb.h"
    #include "hello.pb.h"
    
    ABSL_FLAG(uint16_t, port, 50051, "Server port for the service");
    
    class GreeterServiceTmpl final : public hello::Greeter::Service{
        grpc::Status SayHello(grpc::ServerContext* context, 
            const hello::HelloRequest* request, 
            hello::HelloReply* reply
        ) override {
            std::string prefix("Hello ");
    
            reply->set_message(prefix + request->name());
            return grpc::Status::OK;
        }
    };
    
    void RunServer(uint16_t port){
        std::string server_address = absl::StrFormat("0.0.0.0:%d", port);
        GreeterServiceTmpl service;
    
        grpc::EnableDefaultHealthCheckService(true);
        grpc::reflection::InitProtoReflectionServerBuilderPlugin();
    
        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();
    }
    
    int main(int argc, char** argv){
        absl::ParseCommandLine(argc, argv);
        RunServer(absl::GetFlag(FLAGS_port));
    
        return 0;
    }
  3. client.cc

    #include "absl/flags/flag.h"
    #include "absl/flags/parse.h"
    
    #include "grpcpp/channel.h"
    #include "grpcpp/client_context.h"
    #include "hello.grpc.pb.h"
    #include "hello.pb.h"
    #include <grpcpp/grpcpp.h>
    #include <iterator>
    #include <memory>
    #include <string>
    
    ABSL_FLAG(std::string, target, "localhost:50051", "Server address");
    
    class GreeterClient{
    private:
        std::unique_ptr<hello::Greeter::Stub> stub_;
    public:
        GreeterClient(std::shared_ptr<grpc::Channel> channel):stub_(hello::Greeter::NewStub(channel)){}
    
        std::string SayHello(const std::string& user){
            hello::HelloRequest request;
            request.set_name(user);
    
            hello::HelloReply reply;
            grpc::ClientContext context;
    
            grpc::Status status = stub_->SayHello(&context, request, &reply);       // 发起请求
    
            if(status.ok()){
                return reply.message();
            }else{
                std::cout << status.error_code() << ": " << status.error_message() << std::endl;
                return "RPC failed";
            }
        }
    };
    
    
    int main(int grac, char** argv){
        absl::ParseCommandLine(grac, argv);
        std::string target_str = absl::GetFlag(FLAGS_target);
        GreeterClient greeter(grpc::CreateChannel(target_str, grpc::InsecureChannelCredentials()));
    
        std::string user("world");
        std::string reply = greeter.SayHello(user);
        std::cout << "Greeter received: " << reply << std::endl;
    
        return 0;
    }
  4. CMakeList.txt

    # Copyright 2018 gRPC authors.
    #
    # Licensed under the Apache License, Version 2.0 (the "License");
    # you may not use this file except in compliance with the License.
    # You may obtain a copy of the License at
    #
    #     http://www.apache.org/licenses/LICENSE-2.0
    #
    # Unless required by applicable law or agreed to in writing, software
    # distributed under the License is distributed on an "AS IS" BASIS,
    # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    # See the License for the specific language governing permissions and
    # limitations under the License.
    #
    # cmake build file for C++ helloworld example.
    # Assumes protobuf and gRPC have been installed using cmake.
    # See cmake_externalproject/CMakeLists.txt for all-in-one cmake build
    # that automatically builds all the dependencies before building helloworld.
    
    cmake_minimum_required(VERSION 3.8)
    
    project(hello C CXX)
    
    include(common.cmake)
    
    
    # Proto file
    get_filename_component(hw_proto "./hello.proto" ABSOLUTE)
    get_filename_component(hw_proto_path "${hw_proto}" PATH)
    
    # Generated sources
    set(hw_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/hello.pb.cc")
    set(hw_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/hello.pb.h")
    set(hw_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/hello.grpc.pb.cc")
    set(hw_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/hello.grpc.pb.h")
    add_custom_command(
          OUTPUT "${hw_proto_srcs}" "${hw_proto_hdrs}" "${hw_grpc_srcs}" "${hw_grpc_hdrs}"
          COMMAND ${_PROTOBUF_PROTOC}
          ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
            --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
            -I "${hw_proto_path}"
            --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
            "${hw_proto}"
          DEPENDS "${hw_proto}")
    
    # Include generated *.pb.h files
    include_directories("${CMAKE_CURRENT_BINARY_DIR}")
    
    # hw_grpc_proto
    add_library(hw_grpc_proto
      ${hw_grpc_srcs}
      ${hw_grpc_hdrs}
      ${hw_proto_srcs}
      ${hw_proto_hdrs})
    target_link_libraries(hw_grpc_proto
      ${_REFLECTION}
      ${_GRPC_GRPCPP}
      ${_PROTOBUF_LIBPROTOBUF})
    
    # Targets greeter_[async_](client|server)
    foreach(_target
      server client)
      add_executable(${_target} "${_target}.cc")
      target_link_libraries(${_target}
        hw_grpc_proto
        absl::flags
        absl::flags_parse
        ${_REFLECTION}
        ${_GRPC_GRPCPP}
        ${_PROTOBUF_LIBPROTOBUF})
    endforeach()
  5. 编译 proto 文件

    protoc -I . --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` hello.proto
    protoc -I . --cpp_out=. hello.proto

    第一行会生成:hello.grpc.pb.hhello.grpc.pb.cc, 前者包含你的消息(message)的声明,后者包含你的消息类的实现

    第二行会生成:hello.pb.cchello.pb.h,前者包含你的服务(service)的声明,后者包含你的服务类的实现

3. 关键概念

  1. stub

    On the client side, the client has a stub (referred to as just a client in some languages) that provides the same methods as the server.

    stub

    这个词不是蛮好翻译,可以理解把他理解为一个接口,通过这个接口调用远程的函数就像调用本地函数一样。

  2. channel

    A gRPC channel provides a connection to a gRPC server on a specified host and port. It is used when creating a client stub. Clients can specify channel arguments to modify gRPC’s default behavior, such as switching message compression on or off. A channel has state, including connected and idle.

    channel 就是指 server 和 client 之间的连接

4. WireShark 抓包

  1. 设置 protobuf 搜索路径,让 WireShark 认识 protobuf

    Edit->Preferences > Protocols > Protobuf.

  2. 设置 decode,让 WireShark 可以成功的解析 HTTP2 协议

    Analyze -> decode as -> add

参考

  1. gRPC CPP Quickstart
  2. Protobuf Language Guide
  3. Analyzing gRPC message using Wireshark