gRPC 介绍及在嵌入式 Linux 下的成功编译及使用详解
gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 开发。它支持多种编程语言,并且能够运行在不同的环境中,包括嵌入式系统。本文将详细介绍 gRPC,以及如何在嵌入式 Linux 系统下成功编译 gRPC,并结合
protobuf
和protobuf-lite
介绍它们的区别和适用场景。最后,我们将通过一个具体的例子展示如何在 C++ 中使用 gRPC。
gRPC 简介
gRPC 基于 HTTP/2 协议,使用 Protocol Buffers 作为接口定义语言(IDL)和底层消息交换格式。gRPC 的主要特点包括:
- 高效: 使用 HTTP/2 协议,支持多路复用、流式传输和双向通信。
- 强类型: 使用 Protocol Buffers 定义服务接口和消息格式,确保类型安全。
- 支持多种语言: 提供多种编程语言的支持,如 C++, Java, Python, Go, C#, Ruby 等。
- 易于使用: 通过简单易读的
.proto
文件定义服务接口,自动生成客户端和服务器代码。
gRPC 官方文档
github 仓库地址
Protocol Buffers 和 Protocol Buffers Lite
Protocol Buffers(protobuf)是 Google 开发的一种语言中立、平台中立、可扩展的序列化结构数据的方法。它主要用于结构化数据存储和通信协议。protobuf 提供了两种不同的库版本:protobuf
和 protobuf-lite
。
protobuf
- 完整功能: 包含所有 Protocol Buffers 的功能,包括反射能力(reflection)。
- 反射能力: 允许在运行时查询消息类型的信息,这对于需要动态处理消息或实现通用消息解析器的场景非常有用。
- 代码生成: 生成的代码通常更大,因为包含了更多的功能和元数据。
- 性能: 由于包含反射功能,可能会在某些情况下稍微影响性能,但通常差异不大。
protobuf-lite
- 精简功能: 只包含基本的序列化和反序列化功能,不包含反射能力。
- 没有反射能力: 因此在运行时无法查询消息类型的信息,适用于不需要反射的场景。
- 代码生成: 生成的代码更少,只包含基本的序列化和反序列化方法。
- 性能: 由于没有反射功能,通常在内存和 CPU 使用方面表现更好。
option optimize_for
option optimize_for
是 Protocol Buffers 的编译选项,用于指定生成代码的优化方式。它有三个选项:
DEFAULT
: 默认选项,生成包含完整功能的代码,包括反射能力。SPEED
: 优化速度,生成的代码在序列化和反序列化时性能更好,但不包含反射能力。CODE_SIZE
: 优化代码大小,生成的代码更小,但不包含反射能力。
在嵌入式系统中,通常会选择 SPEED
或 CODE_SIZE
来生成更小的代码并提高性能。但在某些需要动态处理消息的场景中,可能仍然需要使用 DEFAULT
选项以保留反射能力。
下载指定版本的 gRPC 源码
为了确保兼容性和稳定性,我们可以下载 gRPC 的指定版本。本文选择使用 gRPC v1.34.0 版本。
git clone --recurse-submodules -b v1.34.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
cd grpc
交叉编译 gRPC
在嵌入式 Linux 系统下编译 gRPC 需要使用交叉编译工具链。以下是一个成功执行交叉编译的 CMake 指令示例。
准备交叉编译工具链
假设你已经有一个适用于 ARM 架构的交叉编译工具链,并且一个 toolchain1.cmake
文件来配置 CMake 使用这个工具链。以下是一个简单的 toolchain1.cmake
示例:
# toolchain1.cmake
# 交叉编译工具链配置文件
# 用于嵌入式Linux系统和RISC-V MCU的交叉编译# 设置系统名称
set(CMAKE_SYSTEM_NAME Linux)# 设置处理器架构变量,可以通过命令行参数传入
# 例如: cmake -DTARGET_ARCH=arm ..
if(NOT DEFINED TARGET_ARCH)set(TARGET_ARCH "arm" CACHE STRING "Target architecture (arm or riscv)")
endif()# 设置ARM工具链路径
set(ARM_TOOLCHAIN_PATH "/opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/x86_64-pokysdk-linux/usr/bin/arm-poky-linux-gnueabi")
# 设置RISC-V工具链路径
set(RISCV_TOOLCHAIN_PATH "/home/tronlong/tina5.0_v1.0/rtos/lichee/rtos/tools/riscv64-elf-x86_64-20201104")# 根据目标架构设置主工具链
if(${TARGET_ARCH} STREQUAL "arm")# ARM Linux工具链配置set(CMAKE_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-gcc)set(CMAKE_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-g++)set(CMAKE_FIND_ROOT_PATH /opt/fsl-imx-x11/4.1.15-2.1.0/sysroots/cortexa7hf-neon-poky-linux-gnueabi/)set(CMAKE_SYSTEM_PROCESSOR arm)# 设置额外的编译标志set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=armv7-a -mfloat-abi=hard -mfpu=neon" CACHE STRING "" FORCE)# 设置链接器set(CMAKE_LINKER ${CMAKE_C_COMPILER})set(CMAKE_AR ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ar)set(CMAKE_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-poky-linux-gnueabi-ranlib)elseif(${TARGET_ARCH} STREQUAL "riscv")# RISC-V工具链配置 (C906核心)set(CMAKE_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)set(CMAKE_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)set(CMAKE_FIND_ROOT_PATH ${RISCV_TOOLCHAIN_PATH}/riscv64-unknown-elf)set(CMAKE_SYSTEM_PROCESSOR riscv)# 设置RISC-V特定的编译标志 (C906核心)set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=rv64gcv0p7 -mabi=lp64d" CACHE STRING "" FORCE)# 设置链接器set(CMAKE_LINKER ${CMAKE_C_COMPILER})set(CMAKE_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)set(CMAKE_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)else()message(FATAL_ERROR "不支持的目标架构: ${TARGET_ARCH}. 请使用 'arm' 或 'riscv'")
endif()# 定义ARM工具链变量,供CMakeLists.txt使用
set(ARM_C_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-gcc)
set(ARM_CXX_COMPILER ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-g++)
set(ARM_AR ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-ar)
set(ARM_RANLIB ${ARM_TOOLCHAIN_PATH}/arm-linux-gnueabi-ranlib)
set(ARM_C_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")
set(ARM_CXX_FLAGS "-march=armv7-a -mfloat-abi=hard -mfpu=neon")# 定义RISC-V工具链变量,供CMakeLists.txt使用
set(RISCV_C_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-gcc)
set(RISCV_CXX_COMPILER ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-g++)
set(RISCV_AR ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ar)
set(RISCV_RANLIB ${RISCV_TOOLCHAIN_PATH}/bin/riscv64-unknown-elf-ranlib)
set(RISCV_C_FLAGS "-march=rv64gcv0p7 -mabi=lp64d")
set(RISCV_CXX_FLAGS "-march=rv64gcv0p7 -mabi=lp64d")# 设置查找规则
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)# 禁用系统库路径
set(CMAKE_SKIP_RPATH TRUE)# 设置交叉编译环境的库和头文件搜索路径
set(CMAKE_SYSROOT ${CMAKE_FIND_ROOT_PATH})
配置 CMake
使用以下 CMake 命令来配置交叉编译环境:
mkdir -p cmake/build
cd cmake/build
cmake .. \-DCMAKE_TOOLCHAIN_FILE=../toolchain1.cmake \-DTARGET_ARCH=arm \-DCMAKE_INSTALL_PREFIX=${HOME}/arm_install \-DgRPC_INSTALL=ON \-DgRPC_BUILD_TESTS=OFF \-DBUILD_SHARED_LIBS=ON \-DCMAKE_BUILD_TYPE=Release \-DgRPC_ZLIB_PROVIDER=module \-DgRPC_CARES_PROVIDER=module \-DgRPC_PROTOBUF_PROVIDER=module \-DgRPC_SSL_PROVIDER=module \-DgRPC_USE_PROTO_LITE=OFF \-DgRPC_BUILD_CSHARP_EXT=OFF \-DgRPC_BUILD_GRPC_CSHARP_PLUGIN=OFF
选项说明
-DCMAKE_TOOLCHAIN_FILE=../toolchain1.cmake
: 指定交叉编译工具链文件。-DTARGET_ARCH=arm
: 指定目标架构为 ARM。-DCMAKE_INSTALL_PREFIX=${HOME}/arm_install
: 指定安装路径。-DgRPC_INSTALL=ON
: 启用 gRPC 安装。-DgRPC_BUILD_TESTS=OFF
: 禁用测试构建以减少编译时间。-DBUILD_SHARED_LIBS=ON
: 构建共享库。-DCMAKE_BUILD_TYPE=Release
: 选择发布版本以优化性能。-DgRPC_ZLIB_PROVIDER=module
: 使用内置的 zlib 模块。-DgRPC_CARES_PROVIDER=module
: 使用内置的 c-ares 模块。-DgRPC_PROTOBUF_PROVIDER=module
: 使用内置的 protobuf 模块。-DgRPC_SSL_PROVIDER=module
: 使用内置的 SSL 模块。-DgRPC_USE_PROTO_LITE=OFF
: 使用完整的 protobuf 库而不是 protobuf-lite。-DgRPC_BUILD_CSHARP_EXT=OFF
: 禁用 C# 扩展构建。-DgRPC_BUILD_GRPC_CSHARP_PLUGIN=OFF
: 禁用 gRPC C# 插件构建。
构建和安装
配置完成后,可以使用以下命令构建和安装 gRPC:
make -j$(nproc)
make install
在 C++ 中使用 gRPC
接下来,我们将通过一个具体的例子展示如何在 C++ 中使用 gRPC。我们将从定义 .proto
文件开始,然后生成相应的代码,最后编写客户端和服务器代码。
1. 定义 .proto
文件
假设我们要定义一个简单的 RPC 服务,该服务有两个消息类型:MyRequest
和 MyResponse
。我们将这些定义在一个 .proto
文件中:
// myservice.proto
syntax = "proto3";package mypackage;// 定义请求消息
message MyRequest {int32 id = 1;string name = 2;
}// 定义响应消息
message MyResponse {string address = 1;repeated int32 numbers = 2;
}// 定义服务
service MyService {rpc GetInfo (MyRequest) returns (MyResponse);
}
2. 生成代码
使用 protoc
编译器生成 C++ 代码。首先确保你已经安装了 protoc
编译器和相关的插件。然后运行以下命令:
protoc -I . --cpp_out=. myservice.proto --grpc_out=. --plugin=protoc-gen-grpc=/path/to/grpc_cpp_plugin
这将生成两个文件:myservice.pb.cc
和 myservice.grpc.pb.cc
。
3. 编写服务器代码
创建一个服务器代码文件 server.cpp
:
// server.cpp
#include <iostream>
#include <memory>
#include <string>#include <grpc++/grpc++.h>
#include "myservice.grpc.pb.h"using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
using mypackage::MyRequest;
using mypackage::MyResponse;
using mypackage::MyService;// 实现服务接口
class MyServiceImpl final : public MyService::Service {Status GetInfo(ServerContext* context, const MyRequest* request, MyResponse* response) override {// 设置响应字段值response->set_address("123 Main St");response->add_numbers(456);response->add_numbers(789);std::cout << "Received request: id=" << request->id() << ", name=" << request->name() << std::endl;return Status::OK;}
};void RunServer() {std::string server_address("0.0.0.0:50051");MyServiceImpl service;// 创建和启动服务器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();
}int main(int argc, char** argv) {RunServer();return 0;
}
4. 编写客户端代码
创建一个客户端代码文件 client.cpp
:
// client.cpp
#include <iostream>
#include <memory>
#include <string>#include <grpc++/grpc++.h>
#include "myservice.grpc.pb.h"using grpc::Channel;
using grpc::ClientContext;
using grpc::Status;
using mypackage::MyRequest;
using mypackage::MyResponse;
using mypackage::MyService;class MyServiceClient {public:MyServiceClient(std::shared_ptr<Channel> channel): stub_(MyService::NewStub(channel)) {}// 调用 GetInfo RPC 方法void GetInfo(int id, const std::string& name) {MyRequest request;request.set_id(id);request.set_name(name);MyResponse response;ClientContext context;// 调用 RPC 方法Status status = stub_->GetInfo(&context, request, &response);if (status.ok()) {std::cout << "Response: address=" << response.address() << std::endl;std::cout << "Numbers: ";for (int number : response.numbers()) {std::cout << number << " ";}std::cout << std::endl;} else {std::cout << "RPC failed: " << status.error_code() << " " << status.error_message() << std::endl;}}private:std::unique_ptr<MyService::Stub> stub_;
};int main(int argc, char** argv) {MyServiceClient client(grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()));client.GetInfo(123, "Alice");return 0;
}
5. 编写 CMakeLists.txt
创建一个 CMakeLists.txt
文件来配置服务器和客户端的构建:
cmake_minimum_required(VERSION 3.10)
project(MyGrpcService)# 查找 gRPC 库
find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)# 生成 protobuf 和 gRPC 代码
include_directories(${PROTOBUF_INCLUDE_DIRS} ${GRPC_INCLUDE_DIRS})
protobuf_generate_cpp(PROTO_SRCS PROTO_HDRS myservice.proto)
grpc_generate_cpp(GRPC_SRCS GRPC_HDRS myservice.proto)# 添加服务器目标
add_executable(myservice_server server.cpp ${PROTO_SRCS} ${PROTO_HDRS} ${GRPC_SRCS} ${GRPC_HDRS})
target_link_libraries(myservice_server ${PROTOBUF_LIBRARIES} ${GRPC_LIBRARIES} grpc++_reflection)# 添加客户端目标
add_executable(myservice_client client.cpp ${PROTO_SRCS} ${PROTO_HDRS} ${GRPC_SRCS} ${GRPC_HDRS})
target_link_libraries(myservice_client ${PROTOBUF_LIBRARIES} ${GRPC_LIBRARIES})
6. 构建服务器和客户端
使用 CMake 构建服务器和客户端:
mkdir -p build
cd build
cmake ..
make
这将生成 myservice_server
和 myservice_client
可执行文件。
7. 运行服务器和客户端
首先运行服务器:
./myservice_server
然后运行客户端:
./myservice_client
你应该会看到服务器接收到请求并返回响应的输出。
protobuf反射能力的作用
这个涉及你是要用protobuf还是使用protobuf-lite。protobuf-lite只包含基本的序列化和反序列化功能,不包含反射能力,当然体积也更小,效率也更高,尤其适合资源受限的嵌入式设备。需要用哪个,需要权衡利弊。
那么反射能力有什么用?
反射能力(Reflection)是指在运行时能够动态地查询和操作对象的类型信息和结构。在Protocol Buffers(protobuf)中,反射能力允许你在不预先知道消息类型的情况下,动态地序列化、反序列化和访问消息字段。这对于需要灵活处理多种消息类型或在运行时生成和解析消息的场景特别有用。
反射能力的具体含义
- 查询消息结构: 你可以查询消息的字段名称、类型、数量等信息。
- 动态访问字段: 你可以动态地访问和修改消息中的字段值。
- 动态生成消息: 在某些情况下,你可以动态地创建和填充消息对象。
动态处理消息的场景
假设你正在开发一个通用的消息处理系统,该系统需要能够处理不同类型的协议缓冲区消息,而这些消息的类型在编译时并不确定。反射能力在这种情况下非常有用。
举例:通用消息处理系统
-
定义多个消息类型:
假设你有多个不同的消息类型定义在不同的.proto
文件中,例如:// message1.proto syntax = "proto3"; message MyMessage1 {int32 id = 1;string name = 2; }
// message2.proto syntax = "proto3"; message MyMessage2 {string address = 1;repeated int32 numbers = 2; }
-
生成反射代码:
在编译时,确保生成的代码中包含反射信息。你可以在.proto
文件中使用option optimize_for = DEFAULT;
(这是默认选项):// message1.proto syntax = "proto3"; option optimize_for = DEFAULT; message MyMessage1 {int32 id = 1;string name = 2; }
// message2.proto syntax = "proto3"; option optimize_for = DEFAULT; message MyMessage2 {string address = 1;repeated int32 numbers = 2; }
-
使用反射能力:
在你的代码中,你可以使用反射来处理这些消息,而不需要提前知道消息的类型。例如:// dynamic_client.cpp #include <google/protobuf/descriptor.h> #include <google/protobuf/message.h> #include <google/protobuf/dynamic_message.h> #include <grpc++/grpc++.h> #include "myservice.grpc.pb.h" #include <iostream> #include <memory>using grpc::Channel; using grpc::ClientContext; using grpc::Status; using mypackage::MyService; using google::protobuf::DescriptorPool; using google::protobuf::DynamicMessageFactory; using google::protobuf::MessageFactory; using google::protobuf::Message;void DynamicGetInfo(const std::string& request_type, int id, const std::string& name) {// 创建通道std::shared_ptr<Channel> channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials());std::unique_ptr<MyService::Stub> stub(MyService::NewStub(channel));// 获取 DescriptorPool 和 Descriptorconst DescriptorPool* pool = DescriptorPool::generated_pool();const Descriptor* request_descriptor = pool->FindMessageTypeByName(request_type);if (!request_descriptor) {std::cerr << "Request type not found: " << request_type << std::endl;return;}// 创建动态消息对象DynamicMessageFactory factory;std::unique_ptr<Message> request(factory.GetPrototype(request_descriptor)->New());request->GetReflection()->SetInt32(request.get(), request_descriptor->FindFieldByName("id"), id);request->GetReflection()->SetString(request.get(), request_descriptor->FindFieldByName("name"), name);// 获取响应描述符const Descriptor* response_descriptor = stub->GetServiceDescriptor()->FindMethodByName("GetInfo")->output_type();if (!response_descriptor) {std::cerr << "Response type not found" << std::endl;return;}// 创建动态响应对象std::unique_ptr<Message> response(factory.GetPrototype(response_descriptor)->New());ClientContext context;// 调用 RPC 方法Status status = stub->GetInfo(&context, *request, response.get());if (status.ok()) {// 使用反射读取响应字段值std::string address = response->GetReflection()->GetString(response.get(), response_descriptor->FindFieldByName("address"));const google::protobuf::FieldDescriptor* numbers_field = response_descriptor->FindFieldByName("numbers");int numbers_count = response->GetReflection()->FieldSize(*response, numbers_field);std::cout << "Response: address=" << address << ", numbers_count=" << numbers_count << std::endl;for (int i = 0; i < numbers_count; ++i) {int number = response->GetReflection()->GetRepeatedInt32(*response, numbers_field, i);std::cout << " number[" << i << "]=" << number << std::endl;}} else {std::cout << "RPC failed: " << status.error_code() << " " << status.error_message() << std::endl;} }int main(int argc, char** argv) {DynamicGetInfo("mypackage.MyRequest", 123, "Alice");return 0; }
场景总结
在这个例子中,通用消息处理系统可以在运行时根据消息的类型动态地创建、序列化、反序列化和访问消息字段。这使得系统非常灵活,能够处理多种不同的消息类型,而不需要在编译时为每种消息类型编写特定的处理代码。
反射能力在以下场景中特别有用:
- 协议解析器: 需要解析和生成多种不同格式的消息时。
- 调试工具: 需要在没有提前知道消息类型的情况下进行调试时。
- 动态消息处理: 需要在运行时决定处理哪种消息类型时。
- 插件系统: 需要在插件中处理不同消息类型的系统。
通过使用反射能力,你可以编写更通用、更灵活的代码,减少重复代码,提高系统的可维护性和扩展性。
Protocol Buffers库 和 gRPC区别
Protocol Buffers 库
Protocol Buffers(简称 protobuf)是由 Google 开发的一种语言中立、平台中立、可扩展的序列化结构数据的方法。它主要用于结构化数据的存储和通信协议。以下是 protobuf 库的主要功能和用途:
1. 数据序列化和反序列化
- 序列化: 将数据结构转换为字节流,以便存储或传输。
- 反序列化: 将字节流转换回数据结构,以便在应用程序中使用。
2. 强类型数据定义
.proto
文件: 使用.proto
文件定义消息结构和其他相关信息。- 代码生成:
protoc
编译器根据.proto
文件生成特定语言的代码,如 C++, Java, Python 等。
3. 高效的二进制格式
- 紧凑: 生成的二进制格式非常紧凑,适合网络传输和存储。
- 快速: 序列化和反序列化操作速度快,对性能影响小。
4. 向后兼容
- 版本控制: 可以通过添加新的字段来更新消息结构,而不破坏旧版本的代码。
- 字段标签: 使用字段标签来标识消息中的字段,确保兼容性。
5. 反射能力(Reflection)
- 运行时信息: 允许在运行时查询消息类型的信息。
- 动态操作: 适用于需要动态处理消息或实现通用消息解析器的场景。
6. 支持多种语言
- 多语言支持: protobuf 支持多种编程语言,包括 C++, Java, Python, Go, C#, Ruby 等。
- 跨平台: 生成的代码可以在不同的操作系统和架构上编译和运行。
示例
假设我们有一个简单的 .proto
文件:
// myservice.proto
syntax = "proto3";package mypackage;message MyRequest {int32 id = 1;string name = 2;
}message MyResponse {string address = 1;repeated int32 numbers = 2;
}
使用 protoc
编译器生成 C++ 代码:
protoc -I . --cpp_out=. myservice.proto
这将生成 myservice.pb.cc
和 myservice.pb.h
文件,其中包含消息类型的实现。
生成的代码的平台无关性
生成的 C++ 代码本身是平台无关的。这意味着你可以在不同的平台上编译和运行这些代码,包括嵌入式 Linux 平台。生成的代码依赖于 Protocol Buffers 和 gRPC 库,因此需要确保这些库在目标平台上正确编译和安装。
gRPC 库
gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 开发。它基于 HTTP/2 协议,使用 Protocol Buffers 作为接口定义语言(IDL)和底层消息交换格式。以下是 gRPC 库的主要功能和用途:
1. 高性能 RPC 框架
- HTTP/2 协议: 支持多路复用、流式传输和双向通信。
- 强类型: 使用 Protocol Buffers 定义服务接口和消息格式,确保类型安全。
- 高效: 由于基于 HTTP/2,gRPC 提供了高效的网络通信。
2. 服务定义
.proto
文件: 使用.proto
文件定义服务接口和消息格式。- 代码生成:
protoc
编译器根据.proto
文件生成客户端和服务器代码。
3. 支持多种语言
- 多语言支持: gRPC 支持多种编程语言,包括 C++, Java, Python, Go, C#, Ruby 等。
- 跨平台: 可以在不同的操作系统和架构上编译和运行。
4. 反射能力(Reflection)
- 服务描述: 允许在运行时查询服务描述信息。
- 动态客户端: 适用于需要动态生成客户端代码或实现通用客户端的场景。
5. 安全性
- SSL/TLS: 支持通过 SSL/TLS 进行安全的通信。
- 身份验证: 提供多种身份验证机制,如 OAuth 2.0。
6. 流式 RPC
- 单向流: 客户端发送多个消息,服务器发送一个响应。
- 服务器流: 客户端发送一个请求,服务器发送多个响应。
- 双向流: 客户端和服务器都可以发送多个消息。
示例
假设我们有一个简单的 .proto
文件:
// myservice.proto
syntax = "proto3";package mypackage;message MyRequest {int32 id = 1;string name = 2;
}message MyResponse {string address = 1;repeated int32 numbers = 2;
}service MyService {rpc GetInfo (MyRequest) returns (MyResponse);
}
使用 protoc
编译器生成 C++ 代码:
protoc -I . --cpp_out=. myservice.proto --grpc_out=. --plugin=protoc-gen-grpc=/path/to/grpc_cpp_plugin
这将生成 myservice.pb.cc
, myservice.pb.h
, myservice.grpc.pb.cc
, 和 myservice.grpc.pb.h
文件,其中包含消息类型和服务接口的实现。
编译报错的解决
先在宿主机上编一份x64下面的grpc, 这个简单,一般没啥问题。在grpc根目录下新建个x64build文件夹。
cd x64build
x64build# cmake ../ -DgRPC_BUILD_TESTS=OFF -DCMAKE_INSTALL_PREFIX=/usr/local ../..
x64build# make -j$(nproc)
x64build# make install
之后更改toolchain.cmake,增加以下配置:
set (_gRPC_CPP_PLUGIN "/usr/local/bin/grpc_cpp_plugin")
set(_gRPC_PROTOBUF_PROTOC_EXECUTABLE "/usr/local/bin/protoc")
之后,重新执行交叉编译即可。最后,成功交叉编译。
总结
本文介绍了 gRPC 的基本概念、Protocol Buffers 和 protobuf-lite 的区别,以及 option optimize_for
的作用。接着,我们详细描述了如何在嵌入式 Linux 系统下成功编译 gRPC,并提供了一个具体的 CMake 配置示例。通过这些步骤,你应该能够在嵌入式 Linux 环境中顺利编译和使用 gRPC。并提供了一个具体的 CMake 配置示例。最后,我们通过一个具体的例子展示了如何在 C++ 中使用 gRPC,包括定义 .proto 文件、生成代码、编写服务器和客户端代码。
希望本文对你有所帮助!如果你有任何问题或建议,请随时在评论区留言。
参考资料
- gRPC 官方文档
- Protocol Buffers 官方文档
- CMake 官方文档
编译问题 https://github.com/grpc/grpc/issues/22826
编译问题 https://github.com/grpc/grpc/issues/22832
通过以上内容,希望你对 gRPC 和 Protocol Buffers 有了更深入的了解,并能够在嵌入式 Linux 系统下成功编译和使用 gRPC。如果你在编译过程中遇到任何问题,欢迎评论区给猫哥留言。