原文相关

 原文作者:Eduard Los
原文地址:https://medium.com/@eddyf1xxxer/bi-directional-streaming-and-introduction-to-grpc-on-asp-net-core-3-0-part-2-d9127a58dcdb
Demo地址:https://github.com/f1xxxer/CustomerGrpc

现在,让我们看一下代码。可以使用Visual Studio UI或使用命令行命令轻松创建gRPC服务项目:dotnet new grpc -n YourGrpcService

在我的解决方案中,gRPC服务器和客户端的代码都在C#中。gRPC服务器正在管理客户连接并处理消息,并将消息广播给所有连接的客户端。客户端接收客户的输入,将其发送到服务器,还接受来自服务器的其他客户端发送的消息。

我们首先查看CustomerGrpc项目中的服务器代码,但在此之前,我想在标准的Startup.csProgram.cs文件中指出一些内容。

public class Startup {
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices (IServiceCollection services) {
services.AddGrpc (opts => {
opts.EnableDetailedErrors = true;
opts.MaxReceiveMessageSize = 4096;
opts.MaxSendMessageSize = 4096;
});
services.AddTransient<IChatRoomService, ChatRoomService> ();
services.AddSingleton<IChatRoomProvider, ChatRoomProvider> ();
} // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure (IApplicationBuilder app, IWebHostEnvironment env) {
if (env.IsDevelopment ()) {
app.UseDeveloperExceptionPage ();
} app.UseRouting (); app.UseEndpoints (endpoints => {
endpoints.MapGrpcService<Services.CustomerService> (); endpoints.MapGet ("/",
async context => {
await context.Response.WriteAsync (
"Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});
}
}

services.AdddGrpc();方法启用了gRPC 。此方法添加了用于构建用于处理gRPC调用的管道的不同服务和中间件。Undercover方法使用GrpcMarkerService类来确保添加了所有必需的gRPC服务,并且还添加了一些对基础HTTP / 2消息进行操作的中间件。您还可以通过GrpcServiceOptions类型提供服务的配置,该类型稍后将在管道中使用。例如,最大传入消息大小可以这样配置:


services.AddGrpc(opts =>
{
opts.EnableDetailedErrors = true;
opts.MaxReceiveMessageSize = 4096;
opts.MaxSendMessageSize = 4096;
});

对于我们来说,另一种有趣的方法是在端点路由中:endpoints.MapGrpcService<Services.CustomerService>();该方法将gRPC服务添加到路由管道中。在ASP.NET Core中,路由管道在中间件和功能之间共享,这意味着我们可以在其中拥有其他请求处理程序,例如MVC控制器。其他请求处理程序与配置的gRPC服务并行工作。见下文:


app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<Services.CustomerService>(); endpoints.MapGet("/", async context =>
{
await context.Response.WriteAsync(
"Communication with gRPC endpoints must be made through a gRPC client. To learn how to create a client, visit: https://go.microsoft.com/fwlink/?linkid=2086909");
});
});

现在我们需要配置Kestrel服务器。

Kestrel gRPC端点:

  • 需要HTTP/2。
  • 应该使用HTTPS进行保护。

HTTP / 2

gRPC需要HTTP / 2,并且ASP.NET Core的gRPC验证HttpRequest.Protocol. 为HTTP/2。

Kestrel 在大多数现代操作系统上都支持Http/2.。默认情况下,Kestrel端点配置为支持HTTP / 1.1和HTTP / 2连接。

HTTPS

用于gRPC的Kestrel端点应使用HTTPS保护。在开发中,https://localhost:5001当存在ASP.NET Core开发证书时,将自动创建HTTPS终结点。无需配置。

在生产中,必须显式配置HTTPS。在我们的情况下,我们有以下内容:


public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
} // Additional configuration is required to successfully run gRPC on macOS.
// For instructions on how to configure Kestrel and gRPC clients on macOS, visit https://go.microsoft.com/fwlink/?linkid=2099682
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.ConfigureKestrel(options =>
{
// Setup a HTTP/2 endpoint without TLS for OSX.
options.ListenLocalhost(5001, o => o.Protocols = HttpProtocols.Http2);
});
webBuilder.UseStartup<Startup>();
});
}

请注意,存在一个已知问题,即macOS不支持带有传输层安全性(Http/2.)的 ASP.NET Core gRPC 。要在macOS上成功运行gRPC服务,需要进行其他配置。这正是我们的情况,这就是为什么我们必须在options.ListenLocalhost(5001, o => o.Protocols = HttpProtocols.Http2);不带TLS的情况下使用HTTP / 2行禁用TLS的原因,只能在应用程序开发期间使用。生产应用程序应始终使用传输安全性。有关更多信息,请参见ASP.NET Core的gRPC中.的安全注意事项。

现在,让我们看一下我们的gRPC客户服务。一开始我们看到的是我们的CustomerService类是从CustomerGrpc.CustomerService.CustomerServiceBase类继承的。

此类基于我们的.proto文件生成,如果您在文件上按F12键,则会看到一堆自动生成的代码,这些代码描述了我们的服务,消息和通信。

在我们班,我们重写父类中定义了两个自定义的方法:JoinCustomerChatSendMessageToChatRoom这基本上是所有的我们的代码是怎么回事。JoinCustomerChat演示了相对简单的请求/响应模式,我们在其中发送JoinCustomerRequest客户信息,并在客户加入JoinCustomerReply 的聊天室RoomId中进行接收。


public override async Task<JoinCustomerReply> JoinCustomerChat (JoinCustomerRequest request, ServerCallContext context)
{
return new JoinCustomerReply { RoomId = await _chatRoomService.AddCustomerToChatRoomAsync (request.Customer) };
}

然后,我们有一个更有趣的SendMessageToChatRoom方法,它是双向流的演示。


public override async Task SendMessageToChatRoom (IAsyncStreamReader<ChatMessage> requestStream, IServerStreamWriter<ChatMessage> responseStream, ServerCallContext context)
{
var httpContext = context.GetHttpContext ();
_logger.LogInformation ($"Connection id: {httpContext.Connection.Id}"); if (!await requestStream.MoveNext ()) {
return;
} _chatRoomService.ConnectCustomerToChatRoom (requestStream.Current.RoomId, Guid.Parse (requestStream.Current.CustomerId), responseStream);
var user = requestStream.Current.CustomerName;
_logger.LogInformation ($"{user} connected"); try {
while (await requestStream.MoveNext ())
{
if (!string.IsNullOrEmpty (requestStream.Current.Message))
{
if (string.Equals (requestStream.Current.Message, "qw!", StringComparison.OrdinalIgnoreCase)) {
break;
}
await _chatRoomService.BroadcastMessageAsync (requestStream.Current);
}
}
}
catch (IOException)
{
_chatRoomService.DisconnectCustomer (requestStream.Current.RoomId, Guid.Parse (requestStream.Current.CustomerId));
_logger.LogInformation ($"Connection for {user} was aborted.");
}
}

让我们仔细看一下该方法的参数。IAsyncStreamReader requestStream表示来自客户端的消息流,我们可以阅读并进行迭代requestStream.MoveNext()。如果有可用消息,则方法返回true;如果没有其他消息(即,流已关闭),则方法返回false。

IServerStreamWriter responseStream 也是消息流,这次它是可写的,用于将消息发送回客户端。

ServerCallContext context 提供一些方便的属性,例如呼叫的HttpContext,HostName等。

该方法中的逻辑并不复杂。当服务器上接到呼叫时,首先我们存储IServerStreamWriter responseStream用于向客户端广播消息的,


_chatRoomService.ConnectCustomerToChatRoom(requestStream.Current.RoomId, Guid.Parse(requestStream.Current.CustomerId), responseStream);

然后在一个while(await requestStream.MoveNext())循环中,我们浏览从客户端收到的消息,并将它们广播到当前已连接的另一个客户端。


await _chatRoomService.BroadcastMessageAsync(requestStream.Current);

BroadcastMessageAsync()方法中的代码将遍历所有连接的客户端的响应流,并将消息写入该流。


foreach (var customer in chatRoom.CustomersInRoom)
{
await customer.Stream.WriteAsync(message);
Console.WriteLine($"Sent message from {message.CustomerName} to {customer.Name}");
}

我们还有一个try / catch块来处理连接失败并从聊天室中删除相应的流。


catch (IOException)
{
_chatRoomService.DisconnectCustomer(requestStream.Current.RoomId, Guid.Parse(requestStream.Current.CustomerId));
_logger.LogInformation($"Connection for {user} was aborted.");
}

这基本上是与gRPC直接相关的所有服务器逻辑。我已经在服务和包装器中创建了一些附加功能,您可以在此处找到。

现在,我们可以简短地查看客户端代码。首先,我们创建GrpcChannel然后将其提供给gRPC客户端。


var channel = GrpcChannel.ForAddress("http://localhost:5001", new GrpcChannelOptions { Credentials = ChannelCredentials.Insecure });
var client = new CustomerService.CustomerServiceClient(channel);

然后,客户端将JoinCustomerChatAsync返回聊天室的RoomId。之后,我们通过执行SendMessageToChatRoom呼叫和开始消息交换来打开或双向流。


using (var streaming = client.SendMessageToChatRoom (new Metadata { new Metadata.Entry ("CustomerName", customer.Name) }))
{
var response = Task.Run (async () =>
{
while (await streaming.ResponseStream.MoveNext ())
{
Console.ForegroundColor = Enum.Parse<ConsoleColor> (streaming.ResponseStream.Current.Color);
Console.WriteLine ($"{streaming.ResponseStream.Current.CustomerName}: {streaming.ResponseStream.Current.Message}");
Console.ForegroundColor = Enum.Parse<ConsoleColor> (customer.ColorInConsole);
}
}); ... while (!string.Equals (line, "qw!", StringComparison.OrdinalIgnoreCase)) {
await streaming.RequestStream.WriteAsync (new ChatMessage {
Color = customer.ColorInConsole,
CustomerId = customer.Id,
CustomerName = customer.Name,
Message = line,
RoomId = joinCustomerReply.RoomId
});
line = Console.ReadLine ();
DeletePrevConsoleLine ();
}
await streaming.RequestStream.CompleteAsync ();
}

打开连接后,我们在后台任务中开始循环,该循环从响应流中读取消息,然后将其显示在控制台中。第二个循环只是从键盘获取输入,并将该输入发送到服务器。

总体而言,代码并不是那么复杂,但是指出了在哪里需要使用或设计选择来使用.net core和c#来实现自己的gRPC服务的地方。

非常感谢作者Eduard Los.给我们的分享

如您喜欢的话不妨点个赞收藏一下吧

【译】 双向流传输和ASP.NET Core 3.0上的gRPC简介的更多相关文章

  1. ASP.NET Core 3.0 上的gRPC服务模板初体验(多图)

    早就听说ASP.NET Core 3.0中引入了gRPC的服务模板,正好趁着家里电脑刚做了新系统,然后装了VS2019的功夫来体验一把.同时记录体验的过程.如果你也想按照本文的步骤体验的话,那你得先安 ...

  2. [译]Writing Custom Middleware in ASP.NET Core 1.0

    原文: https://www.exceptionnotfound.net/writing-custom-middleware-in-asp-net-core-1-0/ Middleware是ASP. ...

  3. [翻译] ASP.NET Core 3.0 的新增功能

    ASP.NET Core 3.0 的新增功能 全文翻译自微软官方文档英文版 What's new in ASP.NET Core 3.0 本文重点介绍了 ASP.NET Core 3.0 中最重要的更 ...

  4. 推荐:【视频教程】ASP.NET Core 3.0 入门

    墙裂推荐了,免费,通俗易懂,唯一可惜的就是不是我录的,更可惜的是人家录制完了快半年了我还没看完... 版权归原作者所有,建议新手还是边看边实践吧,要不然过完一遍发现自己啥也没学会,不要眼高手低 [视频 ...

  5. ASP.NET Core 3.0 gRPC 双向流

    目录 ASP.NET Core 3.0 使用gRPC ASP.NET Core 3.0 gRPC 双向流 ASP.NET Core 3.0 gRPC 认证授权 一.前言 在前一文 <ASP.NE ...

  6. [译]ASP.NET Core 2.0 会话状态

    问题 如何在ASP.NET Core 2.0中存储会话状态? 答案 创建一个空项目,修改Startup类的ConfigureServices()方法,添加会话状态服务和它后台的存储服务: public ...

  7. [译]ASP.NET Core 2.0 系列文章目录

    基础篇 [译]ASP.NET Core 2.0 中间件 [译]ASP.NET Core 2.0 带初始参数的中间件 [译]ASP.NET Core 2.0 依赖注入 [译]ASP.NET Core 2 ...

  8. [译]ASP.NET Core 2.0 中间件

    问题 如何创建一个最简单的ASP.NET Core中间件? 答案 使用VS创建一个ASP.NET Core 2.0的空项目,注意Startup.cs中的Configure()方法: public vo ...

  9. [译]ASP.NET Core 2.0 带初始参数的中间件

    问题 如何在ASP.NET Core 2.0向中间件传入初始参数? 答案 在一个空项目中,创建一个POCO(Plain Old CLR Object)来保存中间件所需的参数: public class ...

  10. [译]ASP.NET Core 2.0 全局配置项

    问题 如何在 ASP.NET Core 2.0 应用程序中读取全局配置项? 答案 首先新建一个空项目,并添加两个配置文件: 1. appsettings.json { "Section1&q ...

随机推荐

  1. 在NET8中使用简化的 AddJwtBearer 认证

    开发环境 系统版本: win10 .NET SDK: NET8 开发工具:vscode 参考引用:使用 dotnet user-jwts 管理开发中的 JSON Web 令牌 注意:以下示例中的端口. ...

  2. 快速认识什么是:Docker

    Docker,一种可以将软件打包到容器中并在任何环境中可靠运行的工具.但什么是容器以及为什么需要容器呢?今天就来一起学快速入门一下Docker吧!希望本文对您有所帮助. 假设您使用 Cobol 构建了 ...

  3. springMVC特点与优势

    1 SpringMVC和Struts2的优略分析 (面试) 共同点: 它们都是表现层框架,都是基于MVC模型编写的. 它们的底层都离不开原始ServletAPI(HttpServletRequest. ...

  4. 吉特日化MES系统&各类化妆品检验标准汇总

    在日化行业中,生产配料过程中,对产品的检验主要分为四大类: (1) 感官指标 (2) 理化指标 (3) 微生物指标 (4) 毒理指标 根据每个产品的不同,其指标会有所不同

  5. 三个月我迁移了100PB数据

    2023年马上结束,这一年做了不少事情,有一项我可以吹好几年,忍不住和大家分享一下啊. 需求 去年底收到一项需求,要求2个月内从某云存储迁移100PB数据到微软云存储,包含几百亿个文件.当时听到这个数 ...

  6. Java 集合(三)ConcurrentHashMap

    一般来讲,通常使用的 HashMap 不是线程安全的,因为没有任何机制来保证每个操作的原子性.在 ConcurrentHashMap 出现之前,可以通过给 HashMap 的每个操作加上唯一的互斥锁来 ...

  7. HDU 2709 Sumset DP 二进制

    原题链接 题意 给我们一个整数k,要求我们将k分成若干个二的整数幂(1, 2, 4, 8...)的加和形式,问我们所有的分法中,本质不同(即某个2的幂的数量不同)的形式有多少种,k最多为1000000 ...

  8. C#/.NET/.NET Core面试宝典(基础版)

    前言 该知识库主要由自己平时学习实践总结.网上优秀文章资料收集(这一部分会标注来源)和社区小伙伴提供三部分组成.欢迎关注我的微信公众号(声明公众号不推广告,纯属个人技术文章分享)回复关键字获取宝库地址 ...

  9. 数据库实践丨MySQL多表join分析

    摘要:在数据库查询中,往往会需要查询多个表的数据,比如查询会员信息同时查询关于这个会员的订单信息,如果分语句查询的话,效率会很低,就需要用到join关键字来连表查询了. Join并行 Join并行1. ...

  10. 5分钟体验代码仓托管、CloudIDE云端代码编辑、调试、运行

    摘要:您将学会如何通过代码托管(CodeHub)创建代码仓,解决软件开发者在跨地域协同.多分支并发.代码版本管理.安全性等方面的问题. 本文分享自华为云社区<5分钟体验代码仓托管.CloudID ...