Asp.Net Core Grpc 入門實踐
阿新 • • 發佈:2021-02-16
# Grpc簡介
gRPC 是一種與語言無關的高效能遠端過程呼叫 (RPC) 框架。
在 gRPC 中,客戶端應用程式可以直接呼叫不同計算機上的伺服器應用程式上的方法,就像它是本地物件一樣,從而更輕鬆地建立分散式應用程式和服務。它基於定義服務的想法,指定了引數和返回型別的遠端過程呼叫的方法。伺服器端實現這個介面並執行grpc服務來處理客戶端的請求,客戶端呼叫相同的方法完成請求。
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215224828522-325135764.png)
[grpc官網](https://www.grpc.io/docs/what-is-grpc/introduction/)
[.NET 上的 gRPC 的簡介](https://docs.microsoft.com/zh-cn/aspnet/core/grpc/?view=aspnetcore-5.0)
gRPC 的主要優點是:
>現代高效能輕量級 RPC 框架。
>協定優先 API 開發,預設使用協議緩衝區(Protocol Buffers),允許與語言無關的實現。
>可用於多種語言的工具,以生成強型別伺服器和客戶端。
>支援客戶端、伺服器和雙向流式處理呼叫。
>使用 Protobuf 二進位制序列化減少對網路的使用。
這些優點使 gRPC 適用於:
> 效率至關重要的輕量級微服務。
> 需要多種語言用於開發的 Polyglot 系統。
> 需要處理流式處理請求或響應的點對點實時服務。
> 低延遲、高度可擴充套件的分散式系統。
> 開發與雲伺服器通訊的移動客戶端。
> 設計一個需要準確、高效且獨立於語言的新協議。
> 分層設計,以啟用擴充套件,例如。身份驗證、負載平衡、日誌記錄和監控等
## Protocol Buffers
[protocol-buffers詳細介紹](https://developers.google.com/protocol-buffers/docs/overview)
在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;
```
## 指定欄位資料型別
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215230559872-1416051658.png)
### 欄位編號
每個欄位都會有一個唯一的欄位編號,這個非常重要。json中傳遞資料是以欄位名為key,protobuf 是以欄位編號為主,所以不要輕易變化編號。
範圍 1 到 15 中的欄位編號需要一個位元組進行編碼,範圍 16 到 2047 中的欄位編號需要兩個位元組。所以需要為使用頻繁的欄位編號預留欄位號1到15,並且為可能新增的元素預留一點欄位號。
## 指定欄位規則
* required:必填效驗?
* optional ?
* repeated 可以出現多次,類似list
## 四種定義方式
[Rpc生命週期對比](https://www.grpc.io/docs/what-is-grpc/core-concepts/#rpc-life-cycle)
### 一元Unary RPC
`rpc SayHello(HelloRequest) returns (HelloResponse);`
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215233306952-1536665425.png)
### 服務流Server streaming Rpcs
`rpc LotsOfReplies(HelloRequest) returns (stream HelloResponse);`
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215233317042-529742184.png)
### 客戶端流Client streaming RPCs
`rpc LotsOfGreetings(stream HelloRequest) returns (HelloResponse);`
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215233337388-223443299.png)
### 雙向流Bidirectional streaming RPCs
`rpc BidiHello(stream HelloRequest) returns (stream HelloResponse);`
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210216074220693-1489156400.png)
## 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](https://github.com/protocolbuffers/protobuf/releases)
> 解壓後,配置環境變數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專案)
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215235120440-49435671.png)
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屬性,
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210215235756695-1910884156.png)
然後編譯,會生成一個服務定義類以及相關的方法。
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210216000319148-977129559.png)
>注意:EmployeeService.EmployeeServiceBase是有Grpc元件根據proto檔案生成的。
```
public class MyEmployeeService : EmployeeService.EmployeeServiceBase
{
private readonly ILogger _logger;
public MyEmployeeService(ILogger logger)
{
_logger = logger;
}
///
/// Unary Rpc
///
///
///
///
public override async Task 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}");
}
}
///
/// 服務流Server streaming Rpcs
///
///
///
///
///
public override async Task GetAll(GetAllReqeust request, IServerStreamWriter 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
});
}
}
///
/// 客戶端流Client streaming RPCs
///
///
///
///
public override async Task AddPhoto(IAsyncStreamReader requestStream, ServerCallContext context)
{
Console.WriteLine("\r\nGrpcServer即將為你演示 客戶端流Client streaming RPCs ");
MetadataProcess(context);
var data = new List();
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 };
}
///
/// 雙向流Bidirectional streaming RPCs
///
///
///
///
///
public override async Task SaveAll(IAsyncStreamReader requestStream, IServerStreamWriter 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檔案(或引用其他管理方案,如線上地址),然後編譯生成解決方案:
![](https://img2020.cnblogs.com/blog/380359/202102/380359-20210216000908316-1597268805.png)
建立通道
```
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();
}
```
然後對接服務端四種服務流方式:
```
///
/// Unary RPC一元RPC
///
static async Task GetByNoAsync(EmployeeServiceClient client, Metadata md)
{
//一元
var response = await client.GetByNoAsync(new GetByNoRequest()
{
No = 1
}, md);
Console.WriteLine($"Reponse:{response}");
}
///
/// server-stream
///
///
///
///
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);
}
}
///
/// client-stream
///
///
///
///
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);
}
///
/// 雙向流
///
///
///
///
static async Task SaveAll(EmployeeServiceClient client, Metadata md)
{
var employees = new List() {
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 }
```
# 擴充套件瞭解
[日誌配置](https://docs.microsoft.com/zh-cn/aspnet/core/grpc/diagnostics?view=aspnetcore-5.0)
[從 gRPC 建立 JSON Web API](https://docs.microsoft.com/zh-cn/aspnet/core/grpc/httpapi?view=aspnetcore-5.0)
# 原始碼地址
[GrpcDemo和GrpcDemo.Client](https://github.com/fancunwei/FiveFan/tree/main)
# 參考資料
[Grpc官網](https://grpc.io/docs/)
[gRPC in C#*2/Go/C++](https://www.bilibili.com/video/BV1eE411T7GC?from=search&seid=9896969874245634164)
感謝觀看,本篇實踐到此