跳过正文
  1. 文章/

【入门】仿照gRPC示例完成的第一个demo

·1056 字·5 分钟
作者
yellow13441
HLSay
时效性提示: 本文写于 2022 年,涉及的 gRPC、Protobuf、CMake、安装路径和构建方式可能已经发生变化。实际操作前请结合当前版本及官方文档确认。

预期读者
#

对gRPC和Protobuf有理论上的了解,缺乏动手完成demo的经验。

安装gRPC和Protobuf
#

gRPC和Protobuf支持主流的编程语言,要安装C++版本的gRPC请参考:Quick start | C++ | gRPC

StaffBook
#

以gRPC安装在默认路径$HOME/.local/grpc,Protobuf安装在$HOME/.local/grpc/third_party/protobuf为例:

设置环境变量
#

export MY_INSTALL_DIR=$HOME/.local

准备proto协议文件
#

# 进入gRPC保存examples的proto协议的地址
cd $MY_INSTALL_DIR/grpc/examples/protos

# 创建staffbook.proto文件
touch staffbook.proto

# 编辑并保存staffbook.proto文件
nano staffbook.proto

staffbook.proto

// See README.txt for information and build instructions.
//
// Note: START and END tags are used in comments to define sections used in
// tutorials.  They are not part of the syntax for Protocol Buffers.
//
// To get an in-depth walkthrough of this file and the related examples, see:
// https://developers.google.com/protocol-buffers/docs/tutorials

// [START declaration]
syntax = "proto3";
package tutorial;

import "google/protobuf/timestamp.proto";
// [END declaration]

// [START messages]
message Employee {
  int32 id = 1; // Unique ID number for this employee.
  string name = 2;
  int32 age = 3;

  enum GenderType {
    OTHERS = 0;
    MALE = 1;
    FEMALE = 2;
  }
  GenderType gender = 4;
  
  string email = 5;
  string phone = 6;

  google.protobuf.Timestamp last_updated = 7;
}

// Our staff book file is just one of these.
message StaffBook {
  repeated Employee employees = 1;
}
// [END messages]

创建并进入项目路径
#

# 创建目录
mkdir -p ../cpp/staffbook

# 进入项目路径
cd ../cpp/staffbook

准备项目需要的文件
#

# 创建需要的三个文件
touch CMakeLists.txt add_employee.cc list_employees.cc

# CMakeLists.txt用于使用CMake命令创建稍后的Makefile
nano CMakeLists.txt

# add_employee.cc提供添加一条员工记录的方法
nano add_employee.cc提供添加一条员工记录的方法

# list_employees.cc列出所有已添加的员工信息
nano list_employees.cc

CMakeLists.txt

# Copyright 2022 yellow13441 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++ staffbook 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 staffbook.

cmake_minimum_required(VERSION 3.5.1)

project(StaffBook C CXX)

include(../cmake/common.cmake)

# Proto file
get_filename_component(sb_proto "../../protos/staffbook.proto" ABSOLUTE)
get_filename_component(sb_proto_path "${sb_proto}" PATH)

# Generated sources
set(sb_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/staffbook.pb.cc")
set(sb_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/staffbook.pb.h")
set(sb_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/staffbook.grpc.pb.cc")
set(sb_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/staffbook.grpc.pb.h")
add_custom_command(
      OUTPUT "${sb_proto_srcs}" "${sb_proto_hdrs}" "${sb_grpc_srcs}" "${sb_grpc_hdrs}"
      COMMAND ${_PROTOBUF_PROTOC}
      ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
        --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
        -I "${sb_proto_path}"
        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
        "${sb_proto}"
      DEPENDS "${sb_proto}")

# Include generated *.pb.h files
include_directories("${CMAKE_CURRENT_BINARY_DIR}")

# sb_grpc_proto
add_library(sb_grpc_proto
  ${sb_grpc_srcs}
  ${sb_grpc_hdrs}
  ${sb_proto_srcs}
  ${sb_proto_hdrs})
target_link_libraries(sb_grpc_proto
  ${_REFLECTION}
  ${_GRPC_GRPCPP}
  ${_PROTOBUF_LIBPROTOBUF})

# Targets greeter_[async_](client|server)
foreach(_target
  add_employee list_employees)
  add_executable(${_target} "${_target}.cc")
  target_link_libraries(${_target}
    sb_grpc_proto
    ${_REFLECTION}
    ${_GRPC_GRPCPP}
    ${_PROTOBUF_LIBPROTOBUF})
endforeach()

add_employee.cc

// See README.txt for information and build instructions.

#include <ctime>
#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string>

#include "staffbook.pb.h"

using namespace std;

using google::protobuf::util::TimeUtil;

// This function fills in a Employee message based on user input.
void PromptForStaff(tutorial::Employee* employee) {
  cout << "Enter employee ID number: ";
  int id;
  cin >> id;
  employee->set_id(id);
  cin.ignore(256, '\n');

  cout << "Enter name: ";
  getline(cin, *employee->mutable_name());

  cout << "Enter age: ";
  int age;
  cin >> age;
  employee->set_age(age);
  cin.ignore(256, '\n');

  cout << "Enter gender (M or F): ";
  string gender;
  getline(cin, gender);
  if (gender == "M") {
    employee->set_gender(tutorial::Employee::MALE);
  } else if (gender == "F") {
    employee->set_gender(tutorial::Employee::FEMALE);
  } else {
    cout << "Unknown gender type. Using default: Other." << endl;
  }
  
  cout << "Enter email address (blank for none): ";
  string email;
  getline(cin, email);
  if (!email.empty()) {
    employee->set_email(email);
  }

  cout << "Enter phone number (blank for none): ";
  string phone;
  getline(cin, phone);
  if (!phone.empty()) {
    employee->set_phone(phone);
  }

  *employee->mutable_last_updated() = TimeUtil::SecondsToTimestamp(time(NULL));
}

// Main function:  Reads the entire staff book from a file,
//   adds one employee based on user input, then writes it back out to the same
//   file.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " STAFF_BOOK_FILE" << endl;
    return -1;
  }

  tutorial::StaffBook staff_book;

  {
    // Read the existing staff book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!input) {
      cout << argv[1] << ": File not found.  Creating a new file." << endl;
    } else if (!staff_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse staff book." << endl;
      return -1;
    }
  }

  // Add an staff.
  PromptForStaff(staff_book.add_employees());

  {
    // Write the new staff book back to disk.
    fstream output(argv[1], ios::out | ios::trunc | ios::binary);
    if (!staff_book.SerializeToOstream(&output)) {
      cerr << "Failed to write staff book." << endl;
      return -1;
    }
  }

  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();

  return 0;
}

list_employees.cc

// See README.txt for information and build instructions.

#include <fstream>
#include <google/protobuf/util/time_util.h>
#include <iostream>
#include <string>

#include "staffbook.pb.h"

using namespace std;

using google::protobuf::util::TimeUtil;

// Iterates though all employees in the StaffBook and prints info about them.
void ListEmployees(const tutorial::StaffBook& staff_book) {
  for (int i = 0; i < staff_book.employees_size(); i++) {
    const tutorial::Employee& employee = staff_book.employees(i);

    cout << "Employee ID: " << employee.id() << endl;
    cout << "  Name: " << employee.name() << endl;
    cout << "  Age: " << employee.age() << endl;
    cout << "  Gender: " << employee.gender() << endl;

    if (employee.email() != "") {
      cout << "  E-mail address: " << employee.email() << endl;
    }

    if (employee.phone() != "") {
      cout << "  Phone number: " << employee.phone() << endl;
    }

    if (employee.has_last_updated()) {
      cout << "  Updated: " << TimeUtil::ToString(employee.last_updated()) << endl;
    }
  }
}

// Main function:  Reads the entire staff book from a file and prints all
//   the information inside.
int main(int argc, char* argv[]) {
  // Verify that the version of the library that we linked against is
  // compatible with the version of the headers we compiled against.
  GOOGLE_PROTOBUF_VERIFY_VERSION;

  if (argc != 2) {
    cerr << "Usage:  " << argv[0] << " STAFF_BOOK_FILE" << endl;
    return -1;
  }

  tutorial::StaffBook staff_book;

  {
    // Read the existing staff book.
    fstream input(argv[1], ios::in | ios::binary);
    if (!staff_book.ParseFromIstream(&input)) {
      cerr << "Failed to parse staff book." << endl;
      return -1;
    }
  }

  ListEmployees(staff_book);

  // Optional:  Delete all global objects allocated by libprotobuf.
  google::protobuf::ShutdownProtobufLibrary();

  return 0;
}

编译项目
#

# 创建编译目录 
mkdir -p cmake/build

cd cmake/build

# 通过CMake命令在当前路径下编译
# -DCMAKE_PREFIX_PATH指明CMake的find_package()方法查找的路径
# ../..指明CMakeLists.txt的位置
cmake -DCMAKE_PREFIX_PATH=$MY_INSTALL_DIR ../..

# 可以看到目录下生成Makefile
ll

# 通过Make命令对CMake生成的Makefile进行编译
# -j指明同时进行尽可能多的任务 以加快编译效率
# 用make --help查看更多信息
make -j

执行项目
#

# 编译完成后可以看到当前目录下出现两个可执行文件
# 分别为add_employee和list_employees

# staffbook.data存储员工信息
# 若不存在 则会在首次执行时创建此文件
./add_employee staffbook.data

# 列出staffbook.data中存储的所有员工信息
./list_employees staffbook.data