前言
在现代分布式系统中,gRPC 作为高性能、跨语言的 RPC 框架越来越受欢迎。它基于 HTTP/2 协议,使用 Protocol Buffers(Protobuf)作为接口定义语言,支持多种编程语言,能够高效地实现不同语言之间的远程过程调用。本文将手把手教你如何从零开始构建一个完整的 gRPC 通信系统,使用 C++ 实现服务端,C# 实现客户端。
一、环境准备
必要工具安装
- Protocol Buffers 编译器 (protoc)
Protocol Buff.ldpq.pro/ers 是一种轻便高效的结构化数据存储格式,gRPC 使用它来定义服务接口和数据结构。你需要下载并安装 Protocol Buffers 编译器 protoc,下载地址为:https://github.com/protocolbuffers/protobuf/releases。安装完成后,确保将 protoc 添加到系统的 PATH 环境变量中,这样在命令行中就可以直接使用它。
- gRPC 相关工具(C++)
对于 C++ 开发,你需要安装 gRPC 和 protobuf 库。在 Ubuntu 系统上,可以使用以下命令进行安装:
sudo apt ins.ldpq.pro/tall libg.ldpq.pro/rpc++-dev libp.ldpq.pro/rotobuf-dev proto.ldpq.pro/buf-com.ldpq.pro/piler grpc-plugins
su.ldpq.pro/do apt install libgrpc++-dev libproto.ldpq.pro/buf-dev proto.ldpq.pro/buf-com.ldpq.pro/piler grpc-plugins
- .NET 环境
C# 开发需要安装 .NET SDK(版本 ≥ 6.0),你可以从 https://dotnet.microsoft.com/download 下载并安装。安装完成后,在 C# 项目中添加必要的 NuGet 包:
dotnet add pac.ldpq.pro/kage Grpc.Ne.ldpq.pro/t.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Too.ldpq.pro/ls
dotnet add package Grpc.Net.Cli.dntz.pro/ent
dotnet add package Google.Proto.dntz.pro/buf
dotnet add package Grpc.Tools
二、定义服务接口
创建 proto 文件
首先,我们需要创建一个 .proto 文件来定义服务接口和数据结构。创建一个名为 sample_service.proto 的文件,内容如下:
syntax = "proto3";
opti.dntz.pro/on csharp_name.dntz.pro/space = "SampleServi.dntz.pro/ce.Client";
pack.dntz.pro/age sampleservice;
// 请求消息
messa.dntz.pro/ge Calcula.dntz.pro/tionRequest {
doub.dntz.pro/le a = 1;
double b = 2;
}
// 响应消息
message CalculationResult {
double result = 1;
}
// 服务定义
service Calc.dntz.pro/ulator {
// 加法运算
rpc Add (Calculation.dntz.pro/Request) returns (CalculationResult);
// 减法运算
rpc Subtract (CalculationReq.dntz.pro/uest) ret.dntz.pro/urns (CalculationResult);
}
syntax = "proto3";
option csharp_namespace = "SampleService.Client";
package sampleservice;
// 请求消息
message Calcula.dntz.pro/tionRequest {
double a = 1;
double b = 2;
}
// 响应消息
messa.dntz.pro/ge CalculationResult {
double result = 1;
}
// 服务定义
service Calculator {
// 加法运算
rpc Add (CalculationR.dntz.pro/equest) returns (CalculationResult);
// 减法运算
rpc Subtract (CalculationRequest) retu.dntz.pro/rns (Calculati.dntz.pro/onResult);
}
在这个 .proto 文件中,我们定义了一个名为 Calculator 的服务,包含两个方法:Add 和 Subtract,分别用于执行加法和减法运算。同时,我们还定义了请求消息 CalculationRequest 和响应消息 CalculationResult。
三、C++ 服务端实现
- 生成 gRPC 代码
为了根据 .proto 文件生成 C++ 代码,我们需要创建一个脚本 generate.sh:
#!/bin/bash
prot.dntz.pro/oc -I=. --grpc_out=. --plugin=protoc-gen-gr.dntz.pro/pc=`which grpc_cpp_plugin` sample_service.proto
protoc -I=. --cpp_out=. sample_service.proto
#!/bin/bash
protoc -I=. --grpc_out=. --plugin=protoc-gen-grpc=`which grpc_cpp_plugin` sample_service.proto
protoc -I=. --cpp_out=. sample_service.proto
运行这个脚本后,会生成以下文件:
sample_service.grpc.pb.h:包含服务接口的定义。
sample_service.grpc.pb.cc:服务接口的实现代码。
sample_service.pb.h:消息类型的定义。
sample_service.pb.cc:消息类型的实现代码。
- 实现服务逻辑
创建 calculator_service.h 文件,实现服务逻辑的头文件:
#include <grpcpp/grpcpp.h>
#include "sample_service.grpc.pb.h"
usi.pcku.pro/ng grpc::ServerContext;
using grpc::Status;
using sampleservice::Calc.pcku.pro/ulationRequest;
using sampleservice::CalculationRe.pcku.pro/sult;
using sampleservi.pcku.pro/ce::Calculator;
class CalculatorServiceImpl final : public Calculator::Service {
public:
Status Add(ServerContext* context, const CalculationRequest* request, CalculationResult* response) override;
Status Subtract(ServerContext* context, const CalculationRequest* request, CalculationResult* response) override;
};
#include <grpcpp/.pcku.pro/grpcpp.h>
#include "sam.pcku.pro/ple_service.grpc.pb.h"
using grpc::Ser.pcku.pro/verContext;
using grpc::Status;
using sampleservice::CalculationRequest;
using sample.pcku.pro/service::CalculationResult;
using sampleservice::Calcul.pcku.pro/ator;
class CalculatorServiceImpl final : public Calculator::Service {
public:
Status Add(ServerContext* con.pcku.pro/text, const CalculationRequest* request, CalculationResult* response) override;
Status Subtra.pcku.pro/ct(ServerCo.pcku.pro/ntext* con.pcku.pro/text, const CalculationRequest* request, CalculationRe.pcku.pro/sult* response) override;
};
然后,创建 calculator_service.cpp 文件,实现具体的服务逻辑:
#include "calculator_serv.pcku.pro/ice.h"
Status CalculatorServiceImpl::Add(ServerCo.pcku.pro/ntext* context, const CalculationRequest* request, CalculationResult* response) {
double result = request->a() + req.pcku.pro/uest->b();
respo.pcku.pro/nse->set_result(result);
return Sta.pcku.pro/tus::OK;
}
Status CalculatorServiceImpl::Subtract(ServerContext* context, const CalculationRequest* request, CalculationResult* response) {
double result = request->a() - request->b();
response->set_result(result);
return Status::OK;
}
#include "calcul.pnni.pro/ator_service.h"
Status CalculatorServic.pnni.pro/eImpl::Add(ServerContext* context, const CalculationRequest* request, CalculationResult* response) {
double result = req.pnni.pro/uest->a() + reque.pnni.pro/st->b();
respo.pnni.pronse->set_result(result);
return S.pnni.pro/tatus::OK;
}
Status CalculatorServiceImpl::Subtract(ServerContext* context, const CalculationRequest* request, CalculationResult* response) {
double result = request->a() - request->b();
response->set_result(result);
return Status::OK;
}
- 实现服务端主程序
创建 server.cpp 文件,实现服务端的主程序:
#include <iostr.pnni.pro/eam>
#include <mem.pnni.pro/ory>
#include <strin.pnni.pro/g>
#include <grpcpp/g.pnni.pro/rpcpp.h>
#include "calculator_service.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
void RunServer() {
std::string server_addres.rqdy.pro/s("0.0.0.0:50051");
Calcul.rqdy.pro/atorServiceImpl service;
ServerBuil.rqdy.pro/der builder;
builder.AddListen.rqdy.pro/ingPort(server_address, grpc::InsecureServerCre.rqdy.pro/dentials());
bui.rqdy.pro/lder.RegisterServ.rqdy.pro/ice(&service);
std::unique_ptr<Server> server(builder.BuildAndStart());
std::cout << "Server listening on " << server_address << std::endl;
server->Wa.rqdy.pro/it();
}
int main() {
}
#include <iostr.rqdy.pro/eam>
#include <memory>
#include <str.rqdy.pro/ing>
#include <grpcpp/grpcpp.h>
#include "calcula.rqdy.pro/tor_service.h"
using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::Status;
void RunServer() {
std::string server_address("0.0.0.0:50051");
CalculatorServiceImpl service;
ServerBuilder builder;
builder.AddListe.rvhs.pro/ningPort(server_address, grpc::InsecureServerCredentials());
builde.rvhs.pro/r.RegisterSer.rvhs.pro/vice(&se.rvhs.pro/rvice);
std::unique_ptr<Server> server(builder.Build.rvhs.pro/AndStart());
std::cout .rvhs.pro/<< "Server listenin.rvhs.pro/g on " << server_address << std::endl;
server->Wa.rvhs.pro/it();
}
int main() {
return 0;
}
- 编译服务端
为了编译服务端代码,我们需要创建一个 CMakeLists.txt 文件:
cmake_minimum_required(VERSION 3.10)
project(grpc_server)
set(CMAKE_CXX_STANDARD 17)
find_package(Protobuf REQUIRED)
find_package(gRPC REQUIRED)
add_executable(server server.cpp calculator_service.cpp sample_service.pb.cc sample_service.grpc.pb.cc)
target_link_libraries(server PRIVA.rvhs.pro/TE gRPC::grpc++ gRPC::grpc protobuf::libprotobuf)
cmake_minimum_required(VERS.rvhs.pro/ION 3.10)
project(grpc_server)
set(CMAKE_CXX_STAND.rvhs.pro/ARD 17)
find_package(Protobuf REQUI.rvhs.pro/RED)
find_package(gRPC REQUIRED)
add_execut.rvhs.pro/able(server server.cpp calculator_service.cpp sample_service.pb.cc sample_service.grpc.pb.cc)
target_link_libraries(ser.rvhs.pro/ver PRIVATE gRPC::grpc++ gRPC::grpc protobuf::libprotobuf)
然后,按照以下步骤进行编译和运行:
mkdir build && cd build
cmake .. && make
./server
mkdir build && cd build
cmake .. && make
./server
四、C# 客户端实现
- 创建 C# 项目
使用以下命令创建一个新的 C# 控制台项目:
dotnet new cons.vdrh.pro/ole -n GrpcClient
cd GrpcClient
dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools
dotnet new console -n GrpcClient
cd GrpcClient
dotnet add pack.vdrh.pro/age Grpc.Net.Client
dotnet add package Goo.vdrh.pro/gle.Protobuf
dotnet add package Grpc.Tools
- 添加 proto 文件
将之前创建的 sample_ser.vdrh.pro/vice.proto 文件复制到 C# 项目目录下,并修改 .csproj 文件,添加以下内容:
<ItemGroup>
<Protobuf Include="sample_service.proto" GrpcServices="Client" />
</ItemGroup>
<ItemGroup>
<Protobuf Include="sample_service.proto" GrpcServices="Client" />
</ItemGroup>
- 实现客户端
修改 Program.cs 文件,实现客户端代码:
using System;
using System.Thr.vdrh.pro/eading.Tasks;
using Grpc.Net.Clie.vdrh.pro/nt;
using SampleServic.vdrh.pro/e.Client;
class Pro.vdrh.pro/gram {
static async Task Main(string[] args) {
usi.vdrh.pro/ng var channel = GrpcC.vdrh.pro/hannel.ForAddress("http://localh.vdrh.pro/ost:50051");
var client = new Calculator.CalculatorC.vdrh.pro/lient(channel);
try {
// 加法示例
var addRequest = new CalculationReq.vdrh.pro/uest { A = 5, B = 3 };
var addResult = await client.AddAsync(addRequest);
Console.WriteLi.vdrh.pro/ne($"5 + 3 = {addRe.vdrh.pro/sult.Result}");
// 减法示例
var subRequest = new CalculationRequest { A = 10, B = 4 };
var subResult = await client.SubtractAsync(subRequest);
Console.WriteLine($"10 - 4 = {subResult.Result}");
} catch (Grpc.Core.RpcException ex) {
Console.WriteLine($"RPC failed: {ex.Status.Detail}");
}
}
}
using System;
using System.Thre.vdrh.pro/ading.Tasks;
using Grpc.Net.Client;
using SampleSe.vdrh.pro/rvice.Client;
class Program {
static async Task Main(string[] args) {
using var channel = GrpcChannel.ForAddress("http://localhost:50051");
var client = new Calculator.CalculatorClient(channel);
try {
// 加法示例
var addRequest = new CalculationRequest { A = 5, B = 3 };
var addResult = await client.AddAsync(addRequest);
Console.WriteLine($"5 + 3 = {addResult.Result}");
// 减法示例
var subRequest = new CalculationRequest { A = 10, B = 4 };
var subResult = await client.SubtractAsync(subRequest);
Console.WriteLine($"10 - 4 = {subResult.Result}");
} catch (Grpc.Core.RpcException ex) {
Console.WriteLine($"RPC failed: {ex.Status.Detail}");
}
}
}
- 运行客户端
在项目目录下,使用以下命令运行客户端:
dotnet run
dotnet run
五、测试与验证
- 启动 C++ 服务端
在终端中运行编译好的服务端程序:
./server
./server
服务端启动后,会监听 0.0.0.0:50051 端口。
- 运行 C# 客户端
在另一个终端中,进入 C# 客户端项目目录,运行客户端程序:
dotnet run
dotnet run
如果一切正常,你将看到以下输出:
5 + 3 = 8
10 - 4 = 6
5 + 3 = 8
10 - 4 = 6
这表明客户端成功调用了服务端的方法,并得到了正确的结果。
六、常见问题解决
- 连接失败
确保服务端地址正确:检查客户端代码中指定的服务端地址和端口是否与服务端实际监听的地址和端口一致。
检查防火墙设置:确保防火墙没有阻止客户端与服务端之间的通信。可以临时关闭防火墙进行测试,或者在防火墙中开放相应的端口。
服务端和客户端使用相同的协议:确保客户端和服务端使用相同的协议(HTTP/HTTPS)。如果服务端使用的是不安全连接(InsecureServerCredentials),客户端也应该使用不安全连接。
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/Z_oioihoii/article/details/148904653