首页 > 其他分享 >.NET 6 创建 gRPC 服务(简单实现)

.NET 6 创建 gRPC 服务(简单实现)

时间:2023-02-20 15:11:29浏览次数:50  
标签:Task Console gRPC 创建 await var new NET public

.NET 6 创建 gRPC 服务

gRPC(https://grpc.io)是一个由Google开发的高性能、开源、跨多种编程语言和通用的远程过程调用协议(RPC)框架,用于客户端和服务端之间的通信,使用HTTP/2协议并将ProtoBuf(https://developers.google.com/protocol-buffers)作为序列工具.

参考资料:

https://www.cnblogs.com/dennisdong/p/17120990.html#5149719

https://learn.microsoft.com/zh-cn/aspnet/core/tutorials/grpc/grpc-start?view=aspnetcore-7.0&tabs=visual-studio

.NET 上的 gRPC 概述 | Microsoft Learn

项目源码地址:https://github.com/DarkRogerZz/simplegRPC

一、新建服务端

新建项目 -- 新建 ASP.NET Core gRPC 服务

默认文件结构

  • Protos/greet.proto:定义 Greeter gRPC,并用于生成 gRPC 服务器资产。

  • Services 文件夹:包含 Greeter 服务的实现。

  • appSettings.json:包含配置数据,如 Kestrel 使用的协议。

  • Program.cs,其中包含:

    • gRPC 服务的入口点。

    • 配置应用行为的代码。

右键项目生成,项目obj/Debug/net6.0/Protos 就会生成对应proto文件的代码

新建proto文件

下载google protobuf

protobuf是一种与语言无关、与平台无关的可扩展机制,用于序列化结构化数据

比如以下代码块

message Person {
  optional string name = 1;
  optional int32 id = 2;
  optional string email = 3;
}

在gRPC通信过程中,会将以下信息自动序列成json对象

引入google protobuf

在项目Protos文件夹下创建Google文件夹,将下载的google protobuf文件中\include\google\protobuf全部放入Google文件夹

创建proto

新建完成 查看下proto文件属性,如果生成操作是无,将 生成操作改为Protobuf complier

proto具体写法可以参考

https://protobuf.dev/programming-guides/proto3/

syntax = "proto3";

//命名空间
option csharp_namespace = "GrpcService.Protos";

//包名称
package ticket;

//引入google struct
import "Protos/Google/struct.proto";

//定义服务
service TicketServer{
	//简单模式(Simple RPC)
	rpc SayHello(HelloRequest) returns (HelloResponse);
	//服务端数据流模式(Server-side streaming RPC)
	rpc QueryTicket(TicketRequest) returns(stream TicketResponse);
	//客户端数据流模式(Client-side streaming RPC)
	rpc BuyTicket(stream TicketRequest) returns(TicketResponse);
	//双向数据流模式 (Bidirectional streaming RPC)
	rpc StramingBothWay(stream TicketRequest) returns (stream TicketResponse);
}

//定义请求和接收内容
message HelloRequest{
	string name = 1;
}

message HelloResponse{
	string message = 1;
}


//google.protobuf.Struct 作用就是不确定字段,但是可以将一个json发送过去,只需要服务端和客户端保持字段一致即可
message TicketRequest{
	google.protobuf.Struct ticketInfo = 1;
}


message TicketResponse{
	string result = 1;
}
新建服务
using Grpc.Core;
using GrpcService.Protos;
using Newtonsoft.Json;

namespace GrpcService.Services
{
    public class TicketService : TicketServer.TicketServerBase
    {
        private readonly ILogger<TicketService> _logger;

        public TicketService(ILogger<TicketService> logger)
        {
            _logger = logger;
        }

        //简单说个hello
        public override Task<HelloResponse> SayHello(HelloRequest request, ServerCallContext context)
        {
            _logger.LogInformation($"Saying hello to {request.Name}");
            return Task.FromResult(new HelloResponse
            {
                Message = $"Hello{request.Name}"
            });
        }

        //查询票价
        public override async Task QueryTicket(TicketRequest request, IServerStreamWriter<TicketResponse> responseStream, ServerCallContext context)
        {
            int price = 10;
            while (!context.CancellationToken.IsCancellationRequested)
            {
                price += 10;
                await responseStream.WriteAsync(new TicketResponse
                {
                    Result = $"票价为{price}"
                });

                await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);
            }
        }

        //买票
        public override async Task<TicketResponse> BuyTicket(IAsyncStreamReader<TicketRequest> requestStream, ServerCallContext context)
        {
            int num = 100000;
            string name = "";
            //处理请求
            await foreach (var req in requestStream.ReadAllAsync())
            {
                Console.WriteLine($"剩余{num}");
                num--;
                var user = JsonConvert.DeserializeObject<TicketInfo>(req.TicketInfo.ToString());
                name = user.Name;
                Console.WriteLine(name);

            }

            return new TicketResponse { Result = $"成功,最后用户:{name},剩余:{num}" };
        }

        public override async Task StramingBothWay(IAsyncStreamReader<TicketRequest> requestStream, IServerStreamWriter<TicketResponse> responseStream, ServerCallContext context)
        {
            // 服务器响应客户端一次
            // 处理请求
            //await foreach (var req in requestStream.ReadAllAsync())
            //{
            //    Console.WriteLine(req);
            //}

            // 请求处理完成之后只响应一次
            //await responseStream.WriteAsync(new TicketResponse
            //{
            //    Code = 200,
            //    Result = true,
            //    Message = $"StramingBothWay 单次响应: {Guid.NewGuid()}"
            //});
            //await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);

            // 服务器响应客户端多次
            // 处理请求
            var readTask = Task.Run(async () =>
            {
                await foreach (var req in requestStream.ReadAllAsync())
                {
                    Console.WriteLine(req);
                }
            });

            // 请求未处理完之前一直响应
            while (!readTask.IsCompleted)
            {
                await responseStream.WriteAsync(new TicketResponse
                {
                   
                    Result = $"StramingBothWay 请求处理完之前的响应: {Guid.NewGuid()}"
                });
                await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);
            }

            // 也可以无限响应客户端
            //while (!context.CancellationToken.IsCancellationRequested)
            //{
            //    await responseStream.WriteAsync(new TicketResponse
            //    {
            //        Code = 200,
            //        Result = true,
            //        Message = $"StreamingFromServer 无限响应: {Guid.NewGuid()}"
            //    });
            //    await Task.Delay(TimeSpan.FromSeconds(3), context.CancellationToken);
            //}
        }
    }

    public class TicketInfo 
    {
        public string Name { get; set; }

        public bool Sex { get; set; }
    }
}

添加证书(可选)
builder.WebHost.ConfigureKestrel(opt =>
{
    var httpPort = builder.Configuration.GetValue<int>("port:http");

    var httpsPort = builder.Configuration.GetValue<int>("port:https");

    opt.Listen(IPAddress.Any, httpPort, listenOpt => listenOpt.UseConnectionLogging());
    opt.Listen(IPAddress.Any, httpsPort, listenOpt =>
    {
        var enableSsl = builder.Configuration.GetValue<bool>("enableSsl");
        if (enableSsl)
        {
            listenOpt.UseHttps("Certs\\cert.pfx","1234.com");
        }
        else
        {
            listenOpt.UseHttps();
        }
        listenOpt.UseConnectionLogging();
    });
});

二、新建客户端

1、新建一个控制台程序,添加所需依赖

2、新建Protos文件夹

3、放入服务端配置好的proto文件

4、添加项目文件引用

编辑项目文件,添加以下内容

<ItemGroup>
		<Protobuf Include="Protos\Ticket.proto" GrpcServices="Client" />
	</ItemGroup>

5、生成项目,proto会自动生成对应类

建立连接

如果启用证书,需要将服务端证书拷贝到客户端/bin/debug/net6.0下

// 建立连接
    private static TicketServer.TicketServerClient CreateClient(bool enableSsl = false)
    {
        GrpcChannel channel;
        if (enableSsl)
        {
            string url = "https://localhost:7000";

            var handle = new HttpClientHandler();
            // 添加证书
            handle.ClientCertificates.Add(new X509Certificate2("Certs\\cert.pfx", "1234.com"));

            // 忽略证书
            handle.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
            channel = GrpcChannel.ForAddress(url, new GrpcChannelOptions
            {
                HttpClient = new HttpClient(handle)
            });
        }
        else
        {
            string url = "http://localhost:5000";
            channel = GrpcChannel.ForAddress(url);
        }

        return new TicketServer.TicketServerClient(channel);
    }

客户端代码

// See https://aka.ms/new-console-template for more information
using Google.Protobuf.WellKnownTypes;
using Grpc.Core;
using Grpc.Net.Client;
using GrpcService.Protos;
using System;
using System.Net.Sockets;
using System.Security.Cryptography.X509Certificates;



//GrpcTest.SayHello();
GrpcTest.QueryTicket();
//GrpcTest.BuyTicket();
//GrpcTest.StreamingBothWay();

Console.ReadLine();

public static class GrpcTest
{


    // 建立连接
    private static TicketServer.TicketServerClient CreateClient(bool enableSsl = false)
    {
        GrpcChannel channel;
        if (enableSsl)
        {
            string url = "https://localhost:7000";

            var handle = new HttpClientHandler();
            // 添加证书
            handle.ClientCertificates.Add(new X509Certificate2("Certs\\cert.pfx", "1234.com"));

            // 忽略证书
            handle.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
            channel = GrpcChannel.ForAddress(url, new GrpcChannelOptions
            {
                HttpClient = new HttpClient(handle)
            });
        }
        else
        {
            string url = "http://localhost:5000";
            channel = GrpcChannel.ForAddress(url);
        }

        return new TicketServer.TicketServerClient(channel);
    }

    //调用就说hello
    public static async void SayHello()
    {

        Console.WriteLine("单次调用模式,参数:Dennis");
        var client = CreateClient(true);

        var result = await client.SayHelloAsync(new HelloRequest { Name = "Dennis" });

        Console.WriteLine($"服务器响应Message={result.Message}");
    }

    //服务端流模式
    public static async void QueryTicket()
    {
        Console.WriteLine("服务端流模式");
        var client = CreateClient(true);
        var result = client.QueryTicket(new TicketRequest
        {
            TicketInfo = new Struct
            {
                Fields =
                {
                    ["Name"] = Value.ForString("Den"),
                    ["Sex"] = Value.ForBool(true)
                }
            }
        }); ;

        await foreach (var resp in result.ResponseStream.ReadAllAsync())
        {

            Console.WriteLine($"服务器响应结果:{resp.Result}");
        }

    }

    //客户端流模式
    public static async void BuyTicket()
    {
        Console.WriteLine("客户端流模式");
        var client = CreateClient(true);
        var result = client.BuyTicket();

        for (int i = 0; i < 10; i++)
        {
            await result.RequestStream.WriteAsync(new TicketRequest
            {
                TicketInfo = new Struct
                {
                    Fields =
                {
                    ["Name"] = Value.ForString("Den"+i),
                    ["Sex"] = Value.ForBool(true)
                }
                }
            });
            await Task.Delay(TimeSpan.FromSeconds(2));
        }

        await result.RequestStream.CompleteAsync();

        var resp = result.ResponseAsync.Result;
        Console.WriteLine($"服务器响应结果:{resp.Result}");
    }

    //双向流模式
    public static async void StreamingBothWay()
    {
        Console.WriteLine("双向流模式");
        var client = CreateClient();
        var result = client.StramingBothWay();

        // 发送请求
        for (var i = 0; i < 5; i++)
        {
            await result.RequestStream.WriteAsync(new TicketRequest
            {
                TicketInfo = new Struct
                {
                    Fields =
                {
                    ["Name"] = Value.ForString("Den"+i),
                    ["Sex"] = Value.ForBool(true)
                }
                }
            });
            await Task.Delay(TimeSpan.FromSeconds(1));
        }

        // 处理响应
        var respTask = Task.Run(async () =>
        {
            await foreach (var resp in result.ResponseStream.ReadAllAsync())
            {
                Console.WriteLine($"Result={resp.Result}");
            }
        });

        // 等待请求发送完毕
        await result.RequestStream.CompleteAsync();

        // 等待响应处理
        await respTask;
    }
}

三、效果

单次响应:

服务端流模式:

客户端流模式:

双向流模式:

标签:Task,Console,gRPC,创建,await,var,new,NET,public
From: https://www.cnblogs.com/DarkRoger/p/17137513.html

相关文章

  • Centos运行.net core程序的多种方式以及相互之间的区别。
     nohupdotnet/www/wwwroot/xxx.dll--urls"http://*:6001;http://*:6002"&此时候的6001和6002端口对应的程序的内存和static变量都是存在同一个堆栈里面,可以做缓存。......
  • MySQL 创建数据库
    1.1进入MySQL命令:mysql-utest-p;1.2查看数据库命令:SHOWDATABASES;1.3新建数据库命令:CREATEDATABASEitem_name;1.4验证是否查看成功命令:SHOW......
  • leveldb.net区块链技术
    leveldb.net工作原理:leveldb为键值对数据库,具有增加,删除,查询功能,利用加密链式结构存储和查询数据。区块(block):在区块链技术中,数据以电子记录的形式被永久储存下来,存放这些......
  • 档案系统leveldb.net集成
    leveldb.net工作原理:leveldb为键值对数据库,具有增加,删除,查询功能,利用加密链式结构存储和查询数据。区块(block):在区块链技术中,数据以电子记录的形式被永久储存下来,存放这些......
  • Java面试宝典_君哥讲解笔记04_java基础面试题——String s=new String(“xyz“);创建了
    java基础面试题目录文章目录​​java基础面试题目录​​​​前言​​​​Strings=newString("xyz");创建了几个StringObject【重要】​​​​全面理解:Strings2="xyz"......
  • 5900系列和TG-NET系列等交换机恢复出厂教程
    在串口下使用命令恢复出厂设置:通过Console线连接交换机并加电,超级终端CRT/PuTTY(波特率9600或者115200)显示如下 当启动到这里的时候按Ctrl+p(不停的按)  这时进入......
  • c#和.net 初见学习笔记(1)
    c#和.net背景介绍之类的就不再重复了,本次随笔记录从零开始学习c#和.net,有过java和python基础版本.net6、visualstudio2022接口和路由个人习惯,学习时先看项目代码,看......
  • 使用python实现渔网创建
    使用python实现渔网创建fromshapely.geometryimportPolygonimportgeopandasasgpddefFishnet(boundary,cell_height,cell_width)->None:#渔网多边形......
  • php FTP操作类( 拷贝、移动、删除文件/创建目录 )
     <?phpnamespaceftp;/***作用:FTP操作类(拷贝、移动、删除文件/创建目录)*/classftp{public$off;//返回操作状态(成功/失败)public......
  • springboot Elasticsearch 实体创建索引设置Date 类型字段失败
    springbootElasticsearch实体创建索引设置Date类型字段失败,需添加以下注解@Field(type=FieldType.Date,format=DateFormat.custom,patter......