Asp.Net Core Grpc 入門實踐

Grpc簡介

gRPC 是一種與語言無關的高性能遠程過程調用 (RPC) 框架。
在 gRPC 中,客戶端應用程序能夠直接調用不一樣計算機上的服務器應用程序上的方法,就像它是本地對象同樣,從而更輕鬆地建立分佈式應用程序和服務。它基於定義服務的想法,指定了參數和返回類型的遠程過程調用的方法。服務器端實現這個接口並運行grpc服務來處理客戶端的請求,客戶端調用相同的方法完成請求。
git

grpc官網
.NET 上的 gRPC 的簡介github

gRPC 的主要優勢是:web

現代高性能輕量級 RPC 框架。
協定優先 API 開發,默認使用協議緩衝區(Protocol Buffers),容許與語言無關的實現。
可用於多種語言的工具,以生成強類型服務器和客戶端。
支持客戶端、服務器和雙向流式處理調用。
使用 Protobuf 二進制序列化減小對網絡的使用。json

這些優勢使 gRPC 適用於:api

效率相當重要的輕量級微服務。
須要多種語言用於開發的 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,而且爲可能添加的元素預留一點字段號。asp.net

指定字段規則

  • required:必填效驗?
  • optional ?
  • repeated 能夠出現屢次,相似list

四種定義方式

Rpc生命週期對比async

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

服務端

演示特別說明:
一、client流採用分批上傳圖片演示。
二、服務端流採用將list數據分批傳回客戶端。

新建Gprc服務項目(或普通asp.netcore web項目)

一、須要依賴如下nuget包

Grpc.AspNetCore
Grpc.Tools
Grpc.Net.Client 控制檯須要
Google.Protobuf

二、而後新建 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++

感謝觀看,本篇實踐到此結束。

相關文章
相關標籤/搜索