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)
源码地址
参考资料
感谢观看,本篇实践到此结束。
Asp.Net Core Grpc 入门实践的更多相关文章
- ASP.NET Core gRPC 入门全家桶
一. 说明 本全家桶现在只包含了入门级别的资料,实战资料更新中. 二.官方文档 gRPC in Asp.Net Core :官方文档 gRPC 官网:点我跳转 三.入门全家桶 正片: ASP.NET ...
- 005.Getting started with ASP.NET Core MVC and Visual Studio -- 【VS开发asp.net core mvc 入门】
Getting started with ASP.NET Core MVC and Visual Studio VS开发asp.net core mvc 入门 2017-3-7 2 分钟阅读时长 本文 ...
- asp.net core轻松入门之MVC中Options读取配置文件
接上一篇中讲到利用Bind方法读取配置文件 ASP.NET Core轻松入门Bind读取配置文件到C#实例 那么在这篇文章中,我将在上一篇文章的基础上,利用Options方法读取配置文件 首先注册MV ...
- Asp.Net Core WebAPI入门整理(三)跨域处理
一.Core WebAPI中的跨域处理 1.在使用WebAPI项目的时候基本上都会用到跨域处理 2.Core WebAPI的项目中自带了跨域Cors的处理,不需要单独添加程序包 3.使用方法简单 ...
- Asp.Net Core WebAPI入门整理(二)简单示例
一.Core WebAPI中的序列化 使用的是Newtonsoft.Json,自定义全局配置处理: // This method gets called by the runtime. Use thi ...
- Asp.Net Core WebAPI入门整理(四)参数获取
一.总结整理,本实例对应.Net Core 2.0版本 1.在.Net Core WebAPI 中对于参数的获取及自动赋值,沿用了Asp.Net MVC的有点,既可以单个指定多个参数,右可以指定Mo ...
- 观看杨老师(杨旭)Asp.Net Core MVC入门教程记录
观看杨老师(杨旭)Asp.Net Core MVC入门教程记录 ASP.NET Core MVC入门 Asp.Net Core启动和配置 Program类,Main方法 Startup类 依赖注入,I ...
- 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生
[转].NET(C#):浅谈程序集清单资源和RESX资源 目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...
- ASP.NET Core gRPC 健康检查的实现方式
一. 前言 gRPC 服务实现健康检查有两种方式,前面在此文 ASP.NET Core gRPC 使用 Consul 服务注册发现 中有提到过,这里归纳整理一下.gRPC 的健康检查,官方是定义了标准 ...
随机推荐
- 阿里云VOD(一)
一.阿里云视频点播 1.功能介绍 视频点播(ApsaraVideo VoD,简称VoD)是集视频采集.编辑.上传.媒体资源管理.自动化转码处理(窄带高清TM).视频审核分析.分发加速于一体的一站式音视 ...
- 消息队列之rabbitmq学习使用
消息队列之rabbitmq学习使用 1.RabbitMQ简介 1.1.什么是RabbitMQ? RabbitMQ是一个开源的消息代理和队列服务器,用来通过普通协议在完全不同的应用之间共享数据,Rabb ...
- FLask的偏函数应用
偏函数 实际上,偏函数主要辅助原函数,作用其实和原函数差不多,不同的是,我们要多次调用原函数的时候,有些参数,我们需要多次手动的去提供值.而偏函数便可简化这些操作,减少函数调用,主要是将一个或多个参数 ...
- uni-app开发经验分享四: 实现文字复制到选择器中
这里分享一个我经常用到的一个方法,主要是用来复制文字内容,具体代码如下: var that=this; if(!document){ uni.setClipboardData({ data:复制的值, ...
- libuv中实现tcp服务器
目录 1.说明 2.libuv的tcp server 3.API简介 3.1.uv_tcp_init 3.2.uv_ip4_addr 3.3.uv_tcp_bind 3.4.uv_listen 3.5 ...
- scrapy-redis非多网址采集的使用
问题描述 默认RedisSpider在启动时,首先会读取redis中的spidername:start_urls,如果有值则根据url构建request对象. 现在的要求是,根据特定关键词采集. 例如 ...
- 源码 redis 分布式锁
https://github.com/SPSCommerce/redlock-py/tree/master/redlock
- calc, support, media各自的含义及用法?
@support主要是用于检测浏览器是否支持CSS的某个属性,其实就是条件判断,如果支持某个属性,你可以写一套样式,如果不支持某个属性,你也可以提供另外一套样式作为替补. calc() 函数用于动态计 ...
- udp聊天器
import socket def send_msg(udp_socket): """获取键盘数据,并将其发送给对方""" # 1. 从键盘 ...
- 消息队列扫盲(RocketMQ 入门)
消息队列扫盲 消息队列顾名思义就是存放消息的队列,队列我就不解释了,别告诉我你连队列都不知道似啥吧? 所以问题并不是消息队列是什么,而是 消息队列为什么会出现?消息队列能用来干什么?用它来干这些事会带 ...