Asp.Net Core Grpc 入门实践
Grpc简介
gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。
在 gRPC 中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,从而更轻松地创建分布式应用程序和服务。它基于定义服务的想法,指定了参数和返回类型的远程过程调用的方法。服务器端实现这个接口并运行grpc服务来处理客户端的请求,客户端调用相同的方法完成请求。
gRPC 的主要优点是:
现代高性能轻量级 RPC 框架。
协定优先 API 开发,默认使用协议缓冲区(Protocol Buffers),允许与语言无关的实现。
可用于多种语言的工具,以生成强类型服务器和客户端。
支持客户端、服务器和双向流式处理调用。
使用 Protobuf 二进制序列化减少对网络的使用。
这些优点使 gRPC 适用于:
效率至关重要的轻量级微服务。
需要多种语言用于开发的 Polyglot 系统。
需要处理流式处理请求或响应的点对点实时服务。
低延迟、高度可扩展的分布式系统。
开发与云服务器通信的移动客户端。
设计一个需要准确、高效且独立于语言的新协议。
分层设计,以启用扩展,例如。身份验证、负载平衡、日志记录和监控等
Protocol Buffers
protocol-buffers详细介绍
在C#中会生成一个名为FirstMessage的类,基本格式如下:
first.proto
syntax="proto3"; //指定协议版本 package my.project;//C# namespace MyProject option csharp_namespace="GrpcDemo.Protos"; //生成C#代码时命名空间 message FirstMessage{ int32 id=1; string name=2; bool is_male=3; }
定义服务:
指定输入HelloRequest和输出HelloReply,以及方法名SayHello
C#会生成对应的类和方法。
// The greeter service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1;
指定字段数据类型
字段编号
每个字段都会有一个唯一的字段编号,这个非常重要。json中传递数据是以字段名为key,protobuf 是以字段编号为主,所以不要轻易变化编号。
范围 1 到 15 中的字段编号需要一个字节进行编码,范围 16 到 2047 中的字段编号需要两个字节。所以需要为使用频繁的字段编号预留字段号1到15,并且为可能添加的元素预留一点字段号。
指定字段规则
- required:必填效验?
- optional ?
- repeated 可以出现多次,类似list
四种定义方式
一元Unary RPC
rpc SayHello(HelloRequest) returns (HelloResponse);
服务流Server streaming Rpcs
rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);
客户端流Client streaming RPCs
rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);
双向流Bidirectional streaming RPCs
rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);
Metadata元数据
可以传递特定的Rpc信息(如身份信息)。形式为键值对。
var md = new Metadata { { "username","zhangsan"}, { "role","administrator"} }; //或者 var headers = new Metadata(); headers.Add("Authorization", $"Bearer {token}");
Channels 渠道
gRPC 通道提供与指定主机和端口上的 gRPC 服务器的连接。
using var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new EmployeeService.EmployeeServiceClient(channel);
环境搭建
解压后,配置环境变量Path: D:\Proto\protoc-3.14.0-win64\bin
然后cmd确认安装成功:
C:\Users\Administrator>protoc --version libprotoc 3.14.0
代码实践
环境说明:
visual studio 2019 16.8.5 C:\Users\Administrator>dotnet --version 5.0.103
服务端
演示特别说明:
1、client流采用分批上传图片演示。
2、服务端流采用将list数据分批传回客户端。
新建Gprc服务项目(或普通asp.netcore web项目)
1、需要依赖以下nuget包
Grpc.AspNetCore
Grpc.Tools
Grpc.Net.Client 控制台需要
Google.Protobuf
2、然后新建 Protos文件夹,定义proto文件
syntax="proto3"; option csharp_namespace="GrpcDemo.Protos"; message Employee{ int32 id=1; int32 no=2; string firstName=3; string lastName=4; float salary=5;//薪水 } message GetByNoRequest{ int32 no=1; } message EmployeeResonse{ Employee employee=1; } message GetAllReqeust{} message AddPhotoRequest{ bytes data=1; } message AddPhotoResponse{ bool isOk=1; } message EmployeeRequest{ Employee employee=1; } service EmployeeService{ //Unary Rpc示例 rpc GetByNo(GetByNoRequest) returns(EmployeeResonse); //server streaming Rpc示例 rpc GetAll(GetAllReqeust) returns(stream EmployeeResonse); //client streaming Rpc示例 rpc AddPhoto(stream AddPhotoRequest) returns(AddPhotoResponse); //双向Rpc示例 rpc SaveAll(stream EmployeeRequest) returns(stream EmployeeResonse); }
设置proto属性,
然后编译,会生成一个服务定义类以及相关的方法。
注意:EmployeeService.EmployeeServiceBase是有Grpc组件根据proto文件生成的。
public class MyEmployeeService : EmployeeService.EmployeeServiceBase { private readonly ILogger<MyEmployeeService> _logger; public MyEmployeeService(ILogger<MyEmployeeService> logger) { _logger = logger; } /// <summary> /// Unary Rpc /// </summary> /// <param name="request"></param> /// <param name="context"></param> /// <returns></returns> public override async Task<EmployeeResonse> GetByNo(GetByNoRequest request, ServerCallContext context) { Console.WriteLine("\r\nGrpcServer即将为你演示 一元Unary Rpc"); MetadataProcess(context); var data = InmemoryData.Employees.FirstOrDefault(m => m.No == request.No); if (data != null) { return await Task.FromResult(new EmployeeResonse() { Employee = data }); } throw new Exception("异常"); } private void MetadataProcess(ServerCallContext context) { var metaData = context.RequestHeaders; foreach (var item in metaData) { _logger.LogInformation($"key:{item.Key},value:{item.Value}"); } } /// <summary> /// 服务流Server streaming Rpcs /// </summary> /// <param name="request"></param> /// <param name="responseStream"></param> /// <param name="context"></param> /// <returns></returns> public override async Task GetAll(GetAllReqeust request, IServerStreamWriter<EmployeeResonse> responseStream, ServerCallContext context) { Console.WriteLine("\r\nGrpcServer即将为你演示 服务流Server streaming Rpcs"); MetadataProcess(context); foreach (var employee in InmemoryData.Employees) { Console.WriteLine($"responseStream.Write:{employee}"); await responseStream.WriteAsync(new EmployeeResonse() { Employee = employee }); } } /// <summary> /// 客户端流Client streaming RPCs /// </summary> /// <param name="requestStream"></param> /// <param name="context"></param> /// <returns></returns> public override async Task<AddPhotoResponse> AddPhoto(IAsyncStreamReader<AddPhotoRequest> requestStream, ServerCallContext context) { Console.WriteLine("\r\nGrpcServer即将为你演示 客户端流Client streaming RPCs "); MetadataProcess(context); var data = new List<byte>(); while (await requestStream.MoveNext()) { Console.WriteLine($"Received:{requestStream.Current.Data.Length}"); data.AddRange(requestStream.Current.Data); } Console.WriteLine($"Received file with{data.Count} bytes"); return new AddPhotoResponse { IsOk = true }; } /// <summary> /// 双向流Bidirectional streaming RPCs /// </summary> /// <param name="requestStream"></param> /// <param name="responseStream"></param> /// <param name="context"></param> /// <returns></returns> public override async Task SaveAll(IAsyncStreamReader<EmployeeRequest> requestStream, IServerStreamWriter<EmployeeResonse> responseStream, ServerCallContext context) { Console.WriteLine("\r\nGrpcServer即将为你演示 双向流Bidirectional streaming RPCs"); while (await requestStream.MoveNext()) { var employee = requestStream.Current.Employee; Console.WriteLine($"requestStream.Current:{employee}"); lock (this) { InmemoryData.Employees.Add(employee); } Console.WriteLine($"responseStream.Write:{employee}"); await responseStream.WriteAsync(new EmployeeResonse() { Employee = employee }); } } } }
客户端
同上面新建Console项目,并引用以下nuget包:
Google.Protobuf
Grpc.Net.Client
Google.Protobuf
Grpc.Tools
新建protos文件夹,复制proto文件(或引用其他管理方案,如在线地址),然后编译生成解决方案:
创建通道
static async Task Main(string[] args) { using var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new EmployeeService.EmployeeServiceClient(channel); var md = new Metadata { { "username","zhangsan"}, { "role","administrator"}, { "Authorization", $"Bearer xxxxxxxxxxxxxxxxxx" } }; Console.WriteLine("\r\nGrpcClient即将为你演示 一元Unary Rpc"); await GetByNoAsync(client, md); Console.WriteLine("\r\nGrpcClient即将为你演示 服务流Server streaming Rpcs"); await GetAll(client, md); Console.WriteLine("\r\nGrpcClient即将为你演示 客户端流Client streaming RPCs "); await AddPhoto(client,md); Console.WriteLine("\r\nGrpcClient即将为你演示 双向流Bidirectional streaming RPCs"); await SaveAll(client, md); Console.WriteLine("Press Any key Exit!"); Console.Read(); }
然后对接服务端四种服务流方式:
/// <summary> /// Unary RPC一元RPC /// </summary> static async Task GetByNoAsync(EmployeeServiceClient client, Metadata md) { //一元 var response = await client.GetByNoAsync(new GetByNoRequest() { No = 1 }, md); Console.WriteLine($"Reponse:{response}"); } /// <summary> /// server-stream /// </summary> /// <param name="client"></param> /// <param name="md"></param> /// <returns></returns> static async Task GetAll(EmployeeServiceClient client, Metadata md) { using var call = client.GetAll(new GetAllReqeust() { }); var responseStream = call.ResponseStream; while (await responseStream.MoveNext()) { Console.WriteLine(responseStream.Current.Employee); } } /// <summary> /// client-stream /// </summary> /// <param name="client"></param> /// <param name="md"></param> /// <returns></returns> static async Task AddPhoto(EmployeeServiceClient client, Metadata md) { FileStream fs = File.OpenRead("Q1.png"); using var call = client.AddPhoto(md); var stram = call.RequestStream; while (true) { byte[] buffer = new byte[1024]; int numRead = await fs.ReadAsync(buffer, 0, buffer.Length); if (numRead == 0) { break; } if (numRead < buffer.Length) { Array.Resize(ref buffer, numRead); } await stram.WriteAsync(new AddPhotoRequest() { Data = ByteString.CopyFrom(buffer) }); } await stram.CompleteAsync(); var res = await call.ResponseAsync; Console.WriteLine(res.IsOk); } /// <summary> /// 双向流 /// </summary> /// <param name="client"></param> /// <param name="md"></param> /// <returns></returns> static async Task SaveAll(EmployeeServiceClient client, Metadata md) { var employees = new List<Employee>() { new Employee(){ Id=10, FirstName="F10", LastName="L10", No=10, Salary=10 }, new Employee(){ Id=11, FirstName="F11", LastName="L11", No=11, Salary=11 }, new Employee(){ Id=12, FirstName="F12", LastName="L12", No=12, Salary=12 }, }; using var call = client.SaveAll(md); var requestStream = call.RequestStream; var responseStream = call.ResponseStream; var responseTask = Task.Run(async () => { while (await responseStream.MoveNext()) { Console.WriteLine($"response:{responseStream.Current.Employee}"); } }); foreach (var employee in employees) { await requestStream.WriteAsync(new EmployeeRequest() { Employee = employee }); } await requestStream.CompleteAsync(); await responseTask; }
效果演示如下:
客户端:
GrpcClient即将为你演示 一元Unary Rpc Reponse:{ "employee": { "id": 1, "no": 1, "firstName": "FN1", "lastName": "LN1", "salary": 1 } } GrpcClient即将为你演示 服务流Server streaming Rpcs { "id": 1, "no": 1, "firstName": "FN1", "lastName": "LN1", "salary": 1 } { "id": 2, "no": 2, "firstName": "FN1", "lastName": "LN1", "salary": 1 } { "id": 3, "no": 3, "firstName": "FN1", "lastName": "LN1", "salary": 1 } { "id": 4, "no": 4, "firstName": "FN1", "lastName": "LN1", "salary": 1 } GrpcClient即将为你演示 客户端流Client streaming RPCs True GrpcClient即将为你演示 双向流Bidirectional streaming RPCs response:{ "id": 10, "no": 10, "firstName": "F10", "lastName": "L10", "salary": 10 } response:{ "id": 11, "no": 11, "firstName": "F11", "lastName": "L11", "salary": 11 } response:{ "id": 12, "no": 12, "firstName": "F12", "lastName": "L12", "salary": 12 } Press Any key Exit!
服务端输出:
GrpcServer即将为你演示 一元Unary Rpc info: GrpcDemo.Services.MyEmployeeService[0] key:authorization,value:Bearer xxxxxxxxxxxxxxxxxx info: GrpcDemo.Services.MyEmployeeService[0] key:user-agent,value:grpc-dotnet/2.35.0.0 info: GrpcDemo.Services.MyEmployeeService[0] key:username,value:zhangsan info: GrpcDemo.Services.MyEmployeeService[0] key:role,value:administrator GrpcServer即将为你演示 服务流Server streaming Rpcs info: GrpcDemo.Services.MyEmployeeService[0] key:user-agent,value:grpc-dotnet/2.35.0.0 responseStream.Write:{ "id": 1, "no": 1, "firstName": "FN1", "lastName": "LN1", "salary": 1 } responseStream.Write:{ "id": 2, "no": 2, "firstName": "FN1", "lastName": "LN1", "salary": 1 } responseStream.Write:{ "id": 3, "no": 3, "firstName": "FN1", "lastName": "LN1", "salary": 1 } responseStream.Write:{ "id": 4, "no": 4, "firstName": "FN1", "lastName": "LN1", "salary": 1 } GrpcServer即将为你演示 客户端流Client streaming RPCs info: GrpcDemo.Services.MyEmployeeService[0] key:authorization,value:Bearer xxxxxxxxxxxxxxxxxx info: GrpcDemo.Services.MyEmployeeService[0] key:user-agent,value:grpc-dotnet/2.35.0.0 info: GrpcDemo.Services.MyEmployeeService[0] key:username,value:zhangsan info: GrpcDemo.Services.MyEmployeeService[0] key:role,value:administrator Received:1024 Received:1024 Received:1024 Received:1024 Received:1024 Received:1024 Received:1024 Received:1024 Received:1024 Received:573 Received file with9789 bytes GrpcServer即将为你演示 双向流Bidirectional streaming RPCs requestStream.Current:{ "id": 10, "no": 10, "firstName": "F10", "lastName": "L10", "salary": 10 } responseStream.Write:{ "id": 10, "no": 10, "firstName": "F10", "lastName": "L10", "salary": 10 } requestStream.Current:{ "id": 11, "no": 11, "firstName": "F11", "lastName": "L11", "salary": 11 } responseStream.Write:{ "id": 11, "no": 11, "firstName": "F11", "lastName": "L11", "salary": 11 } requestStream.Current:{ "id": 12, "no": 12, "firstName": "F12", "lastName": "L12", "salary": 12 } responseStream.Write:{ "id": 12, "no": 12, "firstName": "F12", "lastName": "L12", "salary": 12 }
扩展了解
[从 gRPC 创建 JSON Web API](https://docs.microsoft.com/zh-cn/aspnet/core/grpc/httpapi?view=aspnetcore-5.0)
源码地址
参考资料
感谢观看,本篇实践到此结束。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?