有做医学手术视频的网站,营销型网站建设广告语,广州网站运营专注乐云seo,提供零基础网站建设教学本文来自DotNET技术圈作者#xff1a;溪源一、引子如您所知#xff0c;gRPC是目前比较常见的rpc框架#xff0c;可以方便的作为服务与服务之间的通信基础设施#xff0c;为构建微服务体系提供非常强有力的支持。而基于.NET Core的gRPC.NET 组件截至2019年11月30日的最新版本… 本文来自DotNET技术圈作者溪源一、引子如您所知gRPC是目前比较常见的rpc框架可以方便的作为服务与服务之间的通信基础设施为构建微服务体系提供非常强有力的支持。而基于.NET Core的gRPC.NET 组件截至2019年11月30日的最新版本为2.25.0该版本基于.netstrandard2.1进行能够在.NET Core3.0上非常方便的实现而且还能方便的迁移到基于.NET Core的windows桌面端开发体系。在本文中参考微软官方文档的示例实现了一个从WCF 服务回调机制迁移到gRPC的过程由于时间仓促如有疏漏还望批评指正。第一篇主要从技术层面来分析迁移流程第二篇打算从业务和代码整洁性角度来思考这个问题。1.1、一些新东西1)、使用客户端工厂组件 Grpc.Net.ClientFactory 在新版本中可以使用 Grpc.Net.ClientFactory 支持以依赖注入的形式AddGrpcClient将grpc客户端引入中而无需每一次方法调用都使用 New 关键词进行创建。这对客户端调用来说是极大的方便毕竟随着.NET Core的普及对于许多开发者来说看到 New 关键词其实是很难受的啊。示例以下代码以注册了 GreetClient 并在发送 http 请求前对请求头信息进行修改添加 jwt 标识以便发送带鉴权标识的请求。serviceCollection.AddGrpcClientGreeterClient(o {o.Address new Uri(configuration[address]);}).AddHttpMessageHandlerJwtTokenHeader();1
2
3
4
5
6
7
8
9
public class GreetImpl
{private readonly GreetClient _greetClient;public GreetImpl(GreetClient greetClient){}
}
JwtTokenHeader中的代码段1
2
request.Headers.Authorization new AuthenticationHeaderValue(Bearer, );
HttpResponseMessage response await base.SendAsync(request, cancellationToken);
(以上示例代码仅供参考不支持直接运行且不支持.NET Framework。。)所以到此为止我们在使用gRPC开发时需要能使用的组件包括以下几种Grpc.AspNETCore包这个包用于在asp.net core中提供grpc服务支持在asp.netcore的服务端项目中以nuget安装grpc组件时需要安装这个包。Google.Protobuf组件Protobuf协议的实现。Grpc.AspNetCore.Server gRPC Asp.NET Core服务端核心库Grpc.Core.Api gRPC Core API核心库Grpc.Tools 包内部封装了从proto文件生成gRPC服务端/客户端方法存根的流程。Grpc.CoregRPC核心包。Grpc.Net.ClientgRPC 客户端实现核心库。Grpc.Core.Api gRPC Core API核心库Grpc.Net.CommongRPC 常用方法。Grpc.Net.ClientFactorygRPC客户端工厂方法。仅用于标准库2.1。2)、其他特性支持 SerializationContext.GetBufferWriter 。性能优化。Optimize server’s gRPC message serialization验证协议降级。Validate gRPC response protocol is not downgradedNew Grpc.AspNetCore.Server.Reflection packageLog unsupported request content-type and protocolMajor client performance improvement修bug等。 当然由于各种原因未能亲测。1.2、存在的缺陷目前的grpc的定位仅仅是一种数据传输机制因此本身不包含负载均衡和服务管理的功能一般会引入consul/etcd/zk等框架来实现服务治理。由于最新版本基于标准库2.1进行构建因此该最新版本无法在.net fx上使用因为.netframework最高仅支持到标准库2.0不过只是新版本不支持依然可以使用2.23.2的版本来实现。当然以后也不会支持.netfx了。。二、gRPC通信方式gRPC提供了以下四种传输方式2.1、Simple RPC简单RPC 传输。一般的rpc方法调用一次请求返回一个对象。适用于类似于以前的webapi请求调用的形式。1
rpc Hello (HelloRequest) returns (HelloReply);
2.1、Server-side streaming RPC一种单向流服务端流式RPC客户端向服务端请求后由服务端以流的形式返回多个结果。例如可以用于客户端需要从服务端获取流媒体文件。1
rpc Subscribe (SubscribeRequest) returns (stream StockTickerUpdate);
2.3、Client-Side streaming RPC一种单向流客户端单向流客户端以流的形式传输多个请求服务端返回一个响应结果。例如可以用于客户端需要向服务端推流的场景。1
rpc Subscribe (stream SubscribeRequest) returns (StockTickerUpdate);
2.4、 Bidirectional streaming RPC双向流式rpc。客户端和服务端均可以传输多个请求。例如可以用于游戏中的双向传输。1
rpc Subscribe (stream SubscribeRequest) returns (stream StockTickerUpdate);
总之看起来gRPC能够实现目前所能设想的大部分场景因此也被视为是古老的rpc框架 wcf ( Windows Communication Foundation )的替代者官方专门编写了一本电子书用来给需要从 wcf 转 gRPC的开发者提供指引。具体地址为 https://docs.microsoft.com/zh-cn/dotnet/architecture/grpc-for-wcf-developers/除此之外本人还看到了一些外网作者使用grpc 来移植 wcf的一些博客。1、 https://www.seeleycoder.com/blog/migrating-wcf-to-grpc-netcore/2、https://www.seeleycoder.com/blog/using-wcf-with-dotnetcore/这两篇博客的作者在.NET Core中使用了WCF根据作者的说法在.NET Core2.0中还能使用但是随着3.0的发布他已经不再使用WCF了而是改用了gRPC。三、WCF的通信方式3.1、简述WCF 是.NET框架中非常常用的一种组件在.NET Framework 3.0时被引入它整合了一些历史悠久的技术框架或通信机制诸如 soap、remoting等。由于WCF技术体系庞大学习路线也比较陡峭能够驾驭的往往都是拥有多年工作经验的资深开发者开发者们有时需针对各个阶段的内涵做深入的了解才能开发对应的应用。由于本人使用WCF的经验尚浅以前的项目用得少充其量就用过Remoting所以以下文字均来自网上现有资料的演绎如有疏漏敬请批评指正。WCF中需要定义合约作为通信过程中的沟通方式。通信双方所遵循的通信方式有合约绑定来制定通信期间的安全性有双方约定的安全性层级来定义。3.2、合约(Contract)合约 Contract 是WCF中最重要的基本概念合约的使用分成两个部分一部分是以接口形式体现的合约一部分是基于合约派生出的实现类。合约分成四种类型数据合约 Data Contract 订定双方沟通时的数据格式。服务合约 Service Contract 订定服务的定义。操作合约 Operation Contract 订定服务提供的方法。在维基百科中翻译为营运合约。消息合约 Message Contract 订定在通信期间改写消息内容的规范。在维基百科中提供了一个如下的代码示例。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using System.ServiceModel;
namespace Microsoft.ServiceModel.Samples
{[ServiceContract(Namespace http://Microsoft.ServiceModel.Samples)] // 服务合约public interface ICalculator{[OperationContract] // 操作合约double Adddouble n1, double n2;[OperationContract] // 操作合约double Subtractdouble n1, double n2;[OperationContract] // 操作合约double Multiplydouble n1, double n2;[OperationContract] // 操作合约double Dividedouble n1, double n2;}
}
3.3、协议绑定WCF支持HTTP\TCP\命名管道 Named Pipe 、MSMQ MSMQ 、点对点TCP Peer-To-Peer TCP 等协议。其中对HTTP协议的支持分为:基本HTTP支持\WS-HTTP支持对TCP的协议也支NetTcpBinding\NetPeerTcpBinding等通信方式。从这里可以看出能够驾驭WCF技术的基本上都是.NET开发领域的大牛涉及到如此多的技术栈实在是令人钦佩。由于WCF支持的协议很多所以在进行WCF的客户端和服务端开发时需要使用统一通信的协议并且在编码以及格式上也要一致。维基百科提供了一个设置通信绑定的示例配置文件当然有时候无需通过配置文件来配置wcf的服务信息通过代码创建也同样可行。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
configurationsystem.serviceModel!-- 接口协议 --servicesservice name CalculatorService endpoint address bindingwsHttpBinding bindingConfigurationBinding1contractICalculator //service/services!-- 通信机制 --bindingswsHttpBindingbinding nameBinding1/binding/wsHttpBinding/bindings/system.serviceModel
/configuration
4、代码迁移4.1 迁移WCF的单工通信在WCF中一般默认的契约形式为点对点的请求-响应方式。即客户端发出请求后一直阻塞方法指导服务端响应后才能执行后面的代码。这种模式类似于gRPC中的简单传输机制所以如果从WCF服务迁移到gRPC服务时比较简单纯粹只需根据对应的数据方法来订定我们的服务协议文件 proto 文件。例如大概是这样的1
2
3
4
5
6
[ServiceContract]
public interface ISimpleStockTickerCallback
{[OperationContract]void HelloWorld(string msg);
}
迁移到 gRpc中之后就是这样的实现1
2
3
4
5
6
7
rpc Hello (HelloRequest) returns (google.protobuf.Empty);
message HelloReply{string msg1;
}
message HelloRequest{string msg1;
}
然后再在两端代码中实现方法即可。由于代码过于简单此处省略若干字在引文3中提供了非常完善的Wcf迁移到gRPC的代码流程需要请自取。4.2 迁移WCF的双工通信1、WCF中的双工通信示例在WCF中双工Duplex通信很常用在通信过程中双方都可以向对方发送消息使得很容易的就实现了服务端回调客户端。在这种模式下客户端向服务端调用一个方法然后在服务端回调客户端方法可以理解为双方的位置发生了改变此时的服务端变成了客户端而客户端变成了服务端。如图所示。代码如下服务端1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/// summary
/// 用于回调的Hello方法
/// /summary
[ServiceContract]
public interface HelloCallback
{[OperationContract(IsOneWay true)]void SayHelloworld(string msg);
}
/// summary
/// 用户服务,并回调客户端到HelloCallback
/// /summary
[ServiceContract(SessionMode SessionMode.Required, CallbackContract typeof(HelloCallback))]
public interface UserService
{[OperationContract(IsOneWay true)]void GetUser(string userName);
}
/// summary
/// 用户服务
/// /summary
[ServiceBehavior(InstanceContextMode InstanceContextMode.PerSession)]
public class UserServiceImpl : UserService
{HelloCallback callback;public void GetUser(string userName){Console.Write(userName);OperationContext context OperationContext.Current;callback context.GetCallbackChannelHelloCallback();callback.SayHelloworld(${userName}:hello);}
}
启动服务端程序时需要创建服务端的Host主机信息。1
2
3
4
5
6
7
8
9
10private static ServiceHost StartUserService(){var host new ServiceHost(typeof(UserServiceImpl));var binding new NetTcpBinding(SecurityMode.None);host.AddServiceEndpoint(typeof(UserService), binding,net.tcp://localhost:12384/userservice);host.Open();return host;
}
订定契约HelloCallback用于处理回调的逻辑。订定契约UserService 和 UserServiceImpl并定义了一个 GetUser 方法。客户端订定契约HelloCallback 和客户端的契约实现 HelloCallbackImpl 。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// summary
/// 回调Hello方法
/// /summary
[ServiceContract]
public interface HelloCallback
{[OperationContract(IsOneWay true)]void SayHelloworld(string msg);
}
public class HelloCallbackImpl : HelloCallback
{public void SayHelloworld(string msg){Console.Write(msg);}
}
订定契约UserService用以保持和服务端的契约保持一致。1
2
3
4
5
6
7
8
9
/// summary
/// 用户服务
/// /summary
[ServiceContract(CallbackContract typeof(HelloCallback))]
public interface UserService
{[OperationContract(IsOneWay true)]void GetUser(string userName);
}
客户端启动时连接到服务端。并发送GetUser方法。1
2
3
4
5
6
7
8
9
10
11
private static void GetUser(NetTcpBinding binding){var address new EndpointAddress(net.tcp://localhost:12384/userservice);var factory new DuplexChannelFactoryUserService(typeof(HelloCallbackImpl), binding,address);var context new InstanceContext(new HelloCallbackImpl());var server factory.CreateChannel(context);server.GetUser(zhangssan);}
实现效果如下这是一个典型的WCF双工通信的示例在传统的.NET Framework开发中可能非常常见但是该如何才能迁移到gRPC服务中呢2、gRPC中的代码实现流程说明gRPC中实现此双工通信需要使用来自服务端的单向流来实现但在gRPC中不能直接回调对应的方法而是在服务端将流返回后触发对应客户端代码中的方法来实现这个回调的流程。如图所示代码实现流程1、定义 proto 协议文件请求方法为getUser并返回流。首先定义服务协议文件命名为 userService.proto 文件。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
syntax proto3;option csharp_namespace DulpexGrpcDemo;package DulpexGrpcDemo;service userService {rpc GetUser (HelloRequest) returns (stream HelloReply);rpc GetTest (HelloRequest) returns (HelloReply);
}
message HelloReply{string msg1;
}
message HelloRequest{string msg1;
}
2、服务端实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class UserServiceImpl : userService.userServiceBase{public override async Task GetUser(HelloRequest request, IServerStreamWriterHelloReply responseStream, ServerCallContext context){await DoSomeThing(request.Msg, (msg) { responseStream.WriteAsync(new HelloReply { Msg ${msg}:hello }); });}//处理回调逻辑private async Task DoSomeThing(string msg, Actionstring action){Console.WriteLine(msg);action?.Invoke(msg);}public override TaskHelloReply GetTest(HelloRequest request, ServerCallContext context){Console.WriteLine(request.Msg);return Task.FromResult(new HelloReply { Msg ${request.Msg}:hello });}}
3、客户端实现需要被调用的方法1
2
3
4
5
6
7
8
9
10
11
public interface HelloCallback
{void SayHelloworld(string msg);
}
public class HelloCallbackImpl : HelloCallback
{public void SayHelloworld(string msg){Console.Write(msg);}
}
4、用户服务方法的实现1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class UserServiceImpl{private userService.userServiceClient userServiceClient;private readonly HelloCallback _helloCallback;public UserServiceImpl(userService.userServiceClient serviceClient, HelloCallback helloCallback){userServiceClient serviceClient;_helloCallback helloCallback;}public async Task GetUser(){AsyncServerStreamingCallHelloReply stream userServiceClient.GetUser(new HelloRequest { Msg 张三 });await Helloworld(stream.ResponseStream);}async Task Helloworld(IAsyncStreamReaderHelloReply stream){await foreach (var update in stream.ReadAllAsync()){_helloCallback.SayHelloworld(update.Msg);}}
}
5、客户端程序的入口1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Program{static async Task Main(string[] args){IServiceCollection servicesCollection new ServiceCollection();IConfiguration configuration new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile(appsettings.json, true, false).Build();servicesCollection.AddGrpcClientuserService.userServiceClient(o {o.Address new Uri(https://localhost:5001);});servicesCollection.AddSingletonUserServiceImpl();servicesCollection.AddSingletonHelloCallback, HelloCallbackImpl();var userServiceImpl servicesCollection.BuildServiceProvider().GetServiceUserServiceImpl();await userServiceImpl.GetUser();Console.ReadLine();}}
当然从这个示例中可能会觉得有点奇怪明明可以使用请求-响应的简单RPC模式为什么要使用服务端的单向流来实现了这种单向流中客户端无需等待服务端执行方法执行完而是由服务端完成后续流程后再回调客户端的方法使得流程变得简单清晰。在微软的官方文档参考文献1更适合介绍这个迁移过程的单向流的实现通过实现服务端向客户端推流的形式来介绍只是方法相对而言实现的逻辑比较多而鄙人这个示例则剥离了与让我们理解服务端单向流流程无关的部分使得流程看起来更简单。参考文献[1] 官方文档 https://docs.microsoft.com/zh-cn/dotnet/architecture/grpc-for-wcf-developers/migrate-duplex-services[2] Jon Seeley的官方博客如何迁移将wcf服务迁移到grpchttps://www.seeleycoder.com/blog/migrating-wcf-to-grpc-netcore/[3] Jon Seeley的官方博客如何在.netcore中使用wcfhttps://www.seeleycoder.com/blog/using-wcf-with-dotnetcore/