时效性提示: 本文写于 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.protostaffbook.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.ccCMakeLists.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