Grpc简介

gRPC 是一种与语言无关的高性能远程过程调用 (RPC) 框架。

在 gRPC 中,客户端应用程序可以直接调用不同计算机上的服务器应用程序上的方法,就像它是本地对象一样,从而更轻松地创建分布式应用程序和服务。它基于定义服务的想法,指定了参数和返回类型的远程过程调用的方法。服务器端实现这个接口并运行grpc服务来处理客户端的请求,客户端调用相同的方法完成请求。

grpc官网

.NET 上的 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

四种定义方式

Rpc生命周期对比

一元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);

环境搭建

下载protobuf

解压后,配置环境变量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)

源码地址

GrpcDemo和GrpcDemo.Client

参考资料

Grpc官网

gRPC in C#*2/Go/C++

感谢观看,本篇实践到此结束。

Asp.Net Core Grpc 入门实践的更多相关文章

  1. ASP.NET Core gRPC 入门全家桶

    一. 说明 本全家桶现在只包含了入门级别的资料,实战资料更新中. 二.官方文档 gRPC in Asp.Net Core :官方文档 gRPC 官网:点我跳转 三.入门全家桶 正片: ASP.NET ...

  2. 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 分钟阅读时长 本文 ...

  3. asp.net core轻松入门之MVC中Options读取配置文件

    接上一篇中讲到利用Bind方法读取配置文件 ASP.NET Core轻松入门Bind读取配置文件到C#实例 那么在这篇文章中,我将在上一篇文章的基础上,利用Options方法读取配置文件 首先注册MV ...

  4. Asp.Net Core WebAPI入门整理(三)跨域处理

    一.Core  WebAPI中的跨域处理  1.在使用WebAPI项目的时候基本上都会用到跨域处理 2.Core WebAPI的项目中自带了跨域Cors的处理,不需要单独添加程序包 3.使用方法简单 ...

  5. Asp.Net Core WebAPI入门整理(二)简单示例

    一.Core WebAPI中的序列化 使用的是Newtonsoft.Json,自定义全局配置处理: // This method gets called by the runtime. Use thi ...

  6. Asp.Net Core WebAPI入门整理(四)参数获取

    一.总结整理,本实例对应.Net Core 2.0版本 1.在.Net Core WebAPI 中对于参数的获取及自动赋值,沿用了Asp.Net  MVC的有点,既可以单个指定多个参数,右可以指定Mo ...

  7. 观看杨老师(杨旭)Asp.Net Core MVC入门教程记录

    观看杨老师(杨旭)Asp.Net Core MVC入门教程记录 ASP.NET Core MVC入门 Asp.Net Core启动和配置 Program类,Main方法 Startup类 依赖注入,I ...

  8. 【转】.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 ...

  9. ASP.NET Core gRPC 健康检查的实现方式

    一. 前言 gRPC 服务实现健康检查有两种方式,前面在此文 ASP.NET Core gRPC 使用 Consul 服务注册发现 中有提到过,这里归纳整理一下.gRPC 的健康检查,官方是定义了标准 ...

随机推荐

  1. Android 代码规范大全

    前言 虽然我们项目的代码时间并不长,也没经过太多人手,但代码的规范性依然堪忧,目前存在较多的比较自由的「代码规范」,这非常不利于项目的维护,代码可读性也不够高, 此外,客户端和后端的研发模式也完全不同 ...

  2. kettle数据质量统计

    1.利用Kettle的"分组","JavaScript代码","字段选择"组件,实现数据质量统计.2.熟练掌握"JavaScrip ...

  3. Flask源码关于local的实现

    flask源码关于local的实现 try: # 协程 from greenlet import getcurrent as get_ident except ImportError: try: fr ...

  4. uni-app开发经验分享一: 多页面传值的三种解决方法

    开发了一年的uni-app,在这里总结一些uni-app开发中的问题,提供几个解决方法,分享给大家: 问题描述:一个主页面,需要联通一到两个子页面,子页面传值到主页面,主页面更新 问题难点: 首先我们 ...

  5. Azure Terraform(七)利用Azure DevOps 实现自动化部署基础资源(补充)

    一,引言 之前一篇文章有讲解到利用 利用Azure DevOps 实现自动化部署基础资源,当时 TF 代码没有针对 Azure 各个资源的封装,所有的资源代码全部写在一个 main.tf 文件中.然后 ...

  6. 览器全面禁用三方 Cookie 全栈前端精选 4月16日

    览器全面禁用三方 Cookie 全栈前端精选 4月16日

  7. mysql和oracle的字符拼接方法

    不同的数据库,相应的字符串拼接方式不同,通过对比加深一下记忆. 一.MySQL字符串拼接 1.CONCAT函数 语法格式:CONCAT(char c1, char c2, ..., char cn) ...

  8. Springboot 项目部署到服务器上

    项目部署到服务器上,有两种方式,一种 jar 包,一种 war 包 jar包 部署时,后续的域名配置,SSL证书等在nginx中配置 war包 部署时,后续的域名配置可以在tomcat中配置就好,修改 ...

  9. (九)整合 ElasticSearch框架,实现高性能搜索引擎

    整合 ElasticSearch框架,实现高性能搜索引擎 1.SpringBoot整合ElasticSearch 1.1 核心依赖 1.2 配置文件 1.3 实体类配置 1.4 数据交互层 1.5 演 ...

  10. Spark 将DataFrame所有的列类型改为double

    Spark 将DataFrame所有的列类型改为double 1.单列转化方法 2.循环转变 3.通过:_* 1.单列转化方法 import org.apache.spark.sql.types._ ...