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。如果你在编译过程中遇到任何问题,欢迎评论区给猫哥留言。
相关文章:
gRPC 介绍及在嵌入式 Linux 下的成功编译及使用详解
gRPC 是一个高性能、开源和通用的 RPC 框架,由 Google 开发。它支持多种编程语言,并且能够运行在不同的环境中,包括嵌入式系统。本文将详细介绍 gRPC,以及如何在嵌入式 Linux 系统下成功编译 gRPC,并结合 protobuf 和 …...
C语言教程(十):C 语言函数详解
一、引言 在 C 语言中,函数是一组执行特定任务的代码块。通过将复杂的程序逻辑划分为多个函数,不仅能提高代码的可读性、可维护性,还便于代码的复用。无论是简单的数学计算,还是复杂的系统操作,函数都发挥着核心作用。…...
力扣刷题-热题100题-第35题(c++、python)
146. LRU 缓存 - 力扣(LeetCode)https://leetcode.cn/problems/lru-cache/?envTypestudy-plan-v2&envIdtop-100-liked 双向链表哈希表 内置函数 对于c有list可以充当双向链表,unordered_map充当哈希表;python有OrderedDic…...
LeetCode算法题(Go语言实现)_52
题目 给你一个下标从 0 开始的整数数组 costs ,其中 costs[i] 是雇佣第 i 位工人的代价。 同时给你两个整数 k 和 candidates 。我们想根据以下规则恰好雇佣 k 位工人: 总共进行 k 轮雇佣,且每一轮恰好雇佣一位工人。 在每一轮雇佣中…...
基于尚硅谷FreeRTOS视频笔记——13—HAL库和RTOS时钟源问题
RTOS的时钟源就是系统定时器中断,通俗来说就是系统定时器每中断一次,就扫描一次RTOS,查看RTOS中有没有任务的切换。 但是,系统存在一个HAL_Delay()函数,就是使用的系统定时器中断来执行的函数。 当我们在RTOS中&…...
FPGA入门学习Day1——设计一个DDS信号发生器
目录 一、DDS简介 (一)基本原理 (二)主要优势 (三)与传统技术的对比 二、FPGA存储器 (一)ROM波形存储器 (二)RAM随机存取存储器 (三&…...
JavaScript-立即执行函数(Immediately Invoked Function Expression,IIFE)
立即执行函数(Immediately Invoked Function Expression,IIFE)是 JavaScript 里一种很独特的函数,它在定义后会马上执行。下面会详细介绍它的语法、用途、优点以及注意事项。 一、语法 立即执行函数一般有两种常见的语法形式&am…...
【Leetcode 每日一题 - 补卡】2537. 统计好子数组的数目
问题背景 给你一个整数数组 n u m s nums nums 和一个整数 k k k,请你返回 n u m s nums nums 中 好 子数组的数目。 一个子数组 a r r arr arr 如果有 至少 k k k 对下标 ( i , j ) (i, j) (i,j) 满足 i < j i < j i<j 且 a r r [ i ] a r r [ …...
【工具-Krillin AI】视频翻译、配音、语音克隆于一体的一站式视频多语言转换工具~
Krillin AI 是全能型音视频本地化与增强解决工具。这款简约而强大的工具,集音视频翻译、配音、语音克隆于一身,支持横竖屏格式输出,确保在所有主流平台(哔哩哔哩,小红书,抖音,视频号,…...
常用绑定事件方式有哪几种
绑定事件分为3种: 1、内联模式:将函数名直接作为标签属性的属性值(注意:这里是带括号的,不带括号不生效,但是在vue中可以加括号也可以不加括号,如果需要穿参数就加括号,不需要传参数可以不加&am…...
数据结构之BFS广度优先算法(腐烂的苹果)
队列这个数据结构在很多场景下都有使用,比如在实现二叉树的层序遍历,floodfill问题(等等未完成)中,都需要借助队列的先进先出特性,下面给出这几个问题的解法 经典的二叉树的层序遍历 算法图示,以下图所示的二叉树为例…...
linux 学习 1.开始学习
准备学习linux记录一下学习内容,只会包含必要的知识,和部分演示 我采用的系统是Ubuntu24.04 初始掌握 学习首先需要掌握如何查看帮助手册 man man # man 加任何命令可以看具体命令的帮助手册 man mkdir进入手册按 d(down):往下翻半页u(u…...
Flink-01学习 介绍Flink及上手小项目之词频统计
flink简介 官网 概述: 学习Flink具体包括四个关键概念:流数据的持续处理,事件时间,有状态流处理和状态快照。 Apache Flink 是一个开源的流处理框架,旨在处理批处理和实时数据处理,具有高吞吐量和低延迟的…...
【Linux我做主】探秘gcc/g++和动静态库
TOC Linux编译器gcc/g的使用 github地址 有梦想的电信狗 前言 在软件开发的世界中,编译器如同匠人的工具,将人类可读的代码转化为机器执行的指令。 对于Linux开发者而言,gcc和g是构建C/C程序的核心工具链,掌握它们的原理和使…...
工控系统前端设计(pyqt)
题目源自:白月黑羽的项目实战四-[工控系统前端] 代码已上传至gitcode https://gitcode.com/m0_37662818/Industrial_Control_System_Front_End 心得体会:直接用组态软件或者js吧 项目亮点 tablemodel的使用,绑定了表格和数据风机自定义ite…...
一台 Master 多节点玩转 Kubernetes:sealos 一键部署实践
文章目录 一台 Master 多节点玩转 Kubernetes:sealos 一键部署实践🔗 参考链接🌐 部署环境📦 安装包说明🔧 前期准备🚀 使用 sealos 安装 Kubernetes✅ 验证集群状态📌 后续可做的优化和拓展&am…...
写书的三驾马车
2019年8月19日23:52:28 先亮出我们的兵器组合: GitBook Git Markdown,享受行云流水一般的写作 个人秀 GitBook : 一个基于 Node.js 的文档格式转换工具,支持 Markdown 和 AsciiDoc 两种语法格式,可以输出 HTML、PDF等格式的…...
科学护理进行性核上性麻痹,缓解病痛提升生活质量
进行性核上性麻痹是一种罕见的神经系统变性疾病,患者常出现姿势平衡障碍、吞咽困难、眼球运动异常等症状。通过科学的健康护理,能在一定程度上减轻患者痛苦,提升生活质量。 日常护理,保障安全舒适 患者日常活动时,需确…...
第七章:7.2求方程a*x*x+b*x+c=0的根,用3个函数,分别求当:b*b-4*a*c大于0、等于0和小于0时的根并输出结果。从主函数输入a、b、c的值
//求方程a*x*xb*xc0的根,用3个函数,分别求当:b*b-4*a*c大于0、等于0和小于0时的根并输出结果。 //从主函数输入a、b、c的值 #define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<math.h> void s1(float a, float b, fl…...
优选算法系列(7.BFS 解决最短路问题)
简介: 先走到A,之后弹出A再把A能走到的地方加进去向外扩展把队列里面的元素(B,C)弹出来,再把B,C能到的地方入队列 一直这样那么最短路程就是扩展的层数。 迷宫中离入口最近的出口(me…...
实现定时发送邮件,以及时间同步
定时发送邮件 部署邮件服务 查看有没有安装mailx,安装了 [root192 ~]# rpm -q mailx mailx-12.5-43.fc38.x86_64去网易拿一下授权码,写到配置文件里 vim /etc/mail.rcset fromxxxxxxx163.com set smtpsmtp.163.com set smtp-auth-userxxxxxxx163.com set smtp-auth-passwor…...
Java反射知识点学习笔记
目录 一、定义 二、获取class对象的三种方式 1、Class.forName("全类名") 2、类名.class 3、对象.getClass() 三、案例 1、获取 class 反射对象三种方式 2、利用反射获取构造方法 3、利用反射获取成员变量 4、利用反射获取成员方法 Java反射是一种强大的编…...
Unity ShaderLab引用HLSL文件找不到其中函数
在写Unity Shader的过程中,常常需要将方法封装到HLSL文件中,今天遇到一个这样的报错, 明明hlsl文件路径引用没问题,却引用不到方法 并且将分散文件中的函数复制过来一切正常,最终定位到HLSL的预编译指令中 这指令的…...
【文献笔记】LLM-based control code generation using image recognition
LLM-based control code generation using image recognition 原文代码 标题翻译:基于图像识别的LLM控制代码生成 1. 内容介绍 1.1. 简介 论文提出了一种基于LLM的新方法,通过图像识别从管道仪表图(Piping and Instrumentation Diagrams,…...
算法之贪心算法
贪心算法 贪心算法核心思想常见应用场景典型案例案例一:找零问题案例二:活动选择问题案例三:货仓选址问题 贪心算法的应用详解霍夫曼编码最小生成树Dijkstra最短路径算法 总结 贪心算法 核心思想 贪心算法(Greedy Algorithm&…...
从“链主”到“全链”:供应链数字化转型的底层逻辑
1. 制造业与供应链数字化转型的必然性 1.1. 核心概念与战略重要性 制造业的数字化转型,是利用新一代数字技术(如工业互联网、人工智能、大数据、云计算、边缘计算等)对制造业的整体价值链进行根本性重塑的过程。这不仅涉及技术的应用&#…...
【Windows本地部署n8n工作流自动平台结合内网穿透远程在线访问】
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...
Python中如何加密/解密敏感信息(如用户密码、token)
敏感信息,如用户密码、API密钥、访问令牌(token)、信用卡号以及其他个人身份信息(PII),构成了现代应用程序和系统中最为关键的部分。这些信息一旦被未经授权的第三方获取,可能引发灾难性的后果,从个人隐私泄露到企业经济损失,甚至是大规模的社会安全问题。保护这些敏感…...
Win10如何一键切换IP地址教程
切换IP地址可能对于许多用户来说是一个相对陌生但又可能经常需要进行的操作。无论是出于网络安全、突破网络限制还是仅仅为了测试目的,一键切换IP地址都能带来极大的便利。以下是在 Windows 10 中通过批处理脚本实现一键切换 IP 地址的详细教程: 方法一&…...
2021-11-09 C++三位数平方含有该数
缘由求解,运算函数,哪位大神教一下-编程语言-CSDN问答 void 三位数平方含有该数() {//缘由https://ask.csdn.net/questions/7560152?spm1005.2025.3001.5141int a 100, aa 1000, f 0;while (a < aa){f a*a;while (f > a)if ((f - a) % aa)f …...
高效检测书签网址,告别无效链接烦恼
软件介绍 你是否有过面对浏览器中满满的书签,却不知道哪些网址还“健在”,哪些已经“跑路”的烦恼?别担心,今天就给大家介绍一款神奇的小工具——“网址小卫士”。 检测轻松搞定 还在一个个手动检查书签网址的有效性吗…...
SpringBoot高校学生评教系统设计实现
概述 基于SpringBoot的高校学生评教系统项目,该系统包含了学生评教、教师管理等功能,适合作为JavaWeb学习项目。 主要内容 1. 学生功能模块 查看评教信息:可以查看学期、院系、任课教师、课程名称等信息评价打分功能:可以对课…...
代码随想录算法训练营第二十天
LeetCode题目: 39. 组合总和40. 组合总和 II131. 分割回文串2176. 统计数组中相等且可以被整除的数对(每日一题) 其他: 今日总结 往期打卡 39. 组合总和 跳转: 39. 组合总和 学习: 代码随想录公开讲解 问题: 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 targ…...
C++入门基础:命名空间,缺省参数,函数重载,输入输出
命名空间: C语言是基于C语言的,融入了面向对象编程思想,有了很多有用的库,所以接下来我们将学习C如何优化C语言的不足的。 在C/C语言实践中,在全局作用域中变量,函数,类会有很多,这…...
GPU怎么绑定到服务器上
确认服务器与 GPU 兼容性1:不同的服务器和 GPU 型号连接方式有所不同,要确保所选的 GPU 卡与服务器兼容。可通过服务器和 GPU 的产品文档,或使用服务器厂商提供的兼容性查询工具进行确认。安装前准备:关闭服务器电源,并…...
opencv函数展示2
一、像素操作与算术运算 1.cv2.split() 2. cv2.merge() 3.cv2.add() 4.cv2.bitwise_and() 5.cv2.bitwise_or() 6.cv2.inRange() 二、仿射变换 1.cv2.getRotationMatrix2D() 2.cv2.warpAffine() 3.cv2.flip() 4.cv2.resize() 三、透视变换 1.cv2.getPerspectiveTransform() 2…...
零基础上手Python数据分析 (16):DataFrame 常用统计分析方法
写在前面 —— 超越简单排序,探索数据内在规律,掌握Pandas统计分析基础 上一篇博客,我们学习了如何使用 Pandas 对 DataFrame 进行排序和排名,这使得我们能够更好地组织数据并快速定位关键信息。 然而,仅仅对数据进行排序和排名,还不足以完全理解数据。 要想更深入地解…...
文件系统 软硬连接
🌻个人主页:路飞雪吖~ 🌠专栏:Linux 目录 一、理解文件系统 🌠磁盘结构 二、软硬连接 🌟软硬链接 🌠软链接: 🌠硬链接: 🌟理解软硬链接的应…...
Linux环境基础开发工具使用
本节目标: 1. 学习yum工具,进行软件安装 2. 掌握vim编辑器使用,学会vim的简单配置 3. 掌握gcc/g编译器的使用,并了解其过程,原理 4. 掌握简单gdb使用于调试 5. 掌握简单的Makefile编写,了解其运行思想…...
秘密任务 2.0:如何利用 WebSockets + DTOs 设计实时操作
在之前的文章中,我们探讨了为什么 DTO 是提升 API 效率和安全性的秘密武器。现在,我们进入了一个全新的场景——我们将深入探讨如何通过 WebSockets DTOs 实现实时操作! Agent X 正在进行一项高风险的卧底任务。突然,总部更新了…...
LeetCode hot 100—括号生成
题目 数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。 示例 示例 1: 输入:n 3 输出:["((()))","(()())","(())()","()(())",&…...
2025.04.17【Dendrogram】生信数据可视化:Dendrogram图表详解
Dendrogram customization Go further with ggraph: edge style, general layout, node features, adding labels, and more. Customized circular dendrogram Learn how to build a circular dendrogram with proper labels. 文章目录 Dendrogram customizationCustomized c…...
SDL基础
SDL SDL(Simple DirectMedia Layer)是一个开源的跨平台多媒体开发库,主要用于开发需要图形、音频和输入设备支持的应用程序。它使用C语言编写,提供了简单易用的API,**能够帮助开发者快速实现跨平台的多媒体功能。**SD…...
硬件工程师面试常见问题(2)
第六问:你知道那些常用逻辑电平?TTL与COMS电平可以直接互连吗? 逻辑电平:是数字电路中用于表示二进制逻辑状态(0 和 1)的电压或电流信号范围,是数字系统中器件间信号传输的统一标准。 注:逻辑电…...
Python自学第2天:条件语句,循环语句
条件语句 1.条件判断 score 60 if score > 90:print("优秀") elif score > 60:print("及格") else:print("不及格") 注意: 1、每个条件后面要使用冒号 :,表示接下来是满足条件后要执行的语句块。2、使用缩进来划…...
2025年4月16日华为笔试第一题100分
📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 01. 博物馆展览规划 问题描述 卢小姐是一家著名博物馆的策展人,她需要从众多展品中选择一些组成新的展览。每件展品可以展示不同的历史文化主题,而博物馆希望通过最少的展品数量覆…...
智能体开发的范式革命:Cangjie Magic全景解读与实践思考
引言:当智能体开发遇见仓颉魔法 在人工智能技术日新月异的今天,智能体(Agent)开发正从实验室走向产业应用的核心舞台。2025年3月,仓颉社区推出的Cangjie Magic开源平台,以其创新的设计理念和技术架构,为这一领域带来了…...
LeetCode hot 100—单词搜索
题目 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或…...
基于flask+vue框架的灯饰安装维修系统u49cf(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
系统程序文件列表 项目功能:用户,工单人员,服务项目,订单记录,服务记录,评价记录 开题报告内容 基于 FlaskVue 框架的灯饰安装维修系统开题报告 一、选题背景与意义 (一)选题背景 随着城市化进程的加速与居民生活品质的显著提升…...
C/C++指针
为什么要使用指针 函数的值传递,无法通过调用函数,来修改函数的实参;被调用函数需要提供更多的“返回值”给调用函数;减少值传递时带来的额外开销,提高代码执行效率 指针定义:指针是什么 int age18; /* …...