.NET 6 创建 gRPC 服务(简单实现)
.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
.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;
}
}
三、效果
单次响应:
服务端流模式:
客户端流模式:
双向流模式: