XRPC
是基于BeetleX
扩展一个远程接口调用组件,它提供基于接口的方式来实现远程服务调用,在应用上非常简便。组件提供.NETCore2.1
和.NETStandard2.0
的client版本,因此即使在winfrom
和wpf
也可以使用该组件进行服务调用处理。接下来详细讲解一下XRPC
使用,从简单的hello
到桌面wpf
调用服务、ssl通讯安全和对象注入等功能。
组件提供了两个版本BeetleX.XRPC
对应.NETCore2.1
它同时提供服务和客户端调用功能,BeetleX.XRPC.Clients
是对应Standard2.0
客户端版本,专门针对桌面应用调用而开发。除了这两个组件外还提供了BeetleX.XRPC.Hosting
,这个组件专门为XRPC
服务提供以Hosting
方式运行的支持,如果你想使用DI
那也可以通过这个组件实现。
很多程序的开始都是以Hello
来展示使用,接下来就使用组件构建一个Hello
的通讯服务。组件的所有服务都需要通过接口来描述,所以在制定服务前需要用接口来描述一下服务需求:
public interface IHello { TaskHello(string name); }
以上是一个Hello
服务接口的定义(接口定义要求是所有方法都必须以Task
或Task
作为返回值)。服务实现
[Service(typeof(IHello))] public class HelloImpl : IHello { public TaskHello(string name) { return $"hello {name} {DateTime.Now}".ToTask(); } }
以上是实现服务细节,接下来通过以下代码启动服务:
static void Main(string[] args) { var builder = new HostBuilder() .ConfigureServices((hostContext, services) => { services.UseXRPC(s => { s.ServerOptions.LogLevel = BeetleX.EventArgs.LogType.Trace; s.ServerOptions.DefaultListen.Port = 9090; s.RPCOptions.ParameterFormater = new JsonPacket();//default messagepack }, typeof(Program).Assembly); }); builder.Build().Run(); }
以上是在所有IP.Any
上的9090
端口提供服务。接下来的工作就是如何调用它,XRPC
在使用上设计非常方便,所以在调用上会变得非常简单.
client = new XRPCClient("localhost", 9090); client.Options.ParameterFormater = new JsonPacket();//default messagepack hello = client.Create(); while (true) { Console.Write("Enter you name:"); var name = Console.ReadLine(); var result = await hello.Hello(name); Console.WriteLine(result); }
只需要指定XRPCClient
对应的服务地址和端口,并创建接口即可调用。XRPCClient
和它创建的接口都是线程安全的,因此只需要定义一个即可在并发中使用。
组件提供json
和messagepack
作为参数传递的编码,messagepack
是默认编码使这种编码序列化对象可以达到非常好的效率,但这种编码需要对类的属性进行标记使用也相对麻烦;如果对效率要求不高不想对类进行属性标记可以设置成Json
.如果想实现自己的编码方式可以通过实现以下接口:
public interface IParameterFormater { void Encode(Options rpcOption, object data, PipeStream stream); object Decode(Options rpcOption, Type type, ArraySegmentdata); }
Actor
模式对象Actor
是一种非常高效的业务处理模型,每个实例有着独立线程资源,其行所有为是串行操作,所以它这种线程独立性和无锁机制非常适合高并发业务处理;XPRC
支持远程Actor
创建,并在服务端维持其独立性,在多客户端同时调用同一Actor
行为时服务端会保证其自有的特性运行。
public interface IAmount { Task Income(int value); Task Pay(int value); TaskGetValue(); }
以上是一个简单的数量增加接口,实现的服务如下:
[Service(typeof(IAmount))] public class AmountImpl : IAmount { private int mAmount; public TaskGetValue() { return mAmount.ToTask(); } public Task Income(int value) { mAmount -= value; return Task.CompletedTask; } public Task Pay(int value) { mAmount += value; return Task.CompletedTask; } }
组件在actor
应用并没有特殊的要求,主要是客户端在创建的时候告诉服务端需要创建一个指标识的actor
实例即可,代码如下:
client = new XRPCClient("localhost", 9090); client.Options.ParameterFormater = new JsonPacket();//default messagepack henry = client.Create("henry"); ken = client.Create ("ken");
以上是针对IAmount
创建两个实例,分别是:henry
和ken
.服务端会根据请求的标识在服务端维护各自的actor
实例。多客户端同时创建相同名称的actor
实例怎办?即是多客户端同时创建同一名称的actor
和并发调用,服务端都可保证actor
实例的唯一性(实际应用需要涉及到actor
的状态,信息持久化等,这些就不在这里讨论;XRPC
的这一功能则由https://github.com/IKende/EventNext 提供)。
有时候需要在winfrom
或wpf
中调用服务,这个时候就需要通过BeetleX.XRPC.Clients
来实现调用;它所提供的功能和BeetleX.XRPC
内置的客户端功能是一样的。接下来做一个简单的数据查询,不过这个示例为了符合客户端的需求还针对方法添加了JWT
验证的功能。
public interface IDataService { TaskLogin(string name, string pwd); Task > List(); }
以上是一个简单的数据查询接口,里面添加了一个登陆方法.
[Service(typeof(IDataService))] [TokenFilter] public class DataServiceImpl : IDataService { public Task> List() { return DataHelper.Defalut.Employees.ToTask(); } [SkipActionFilter(typeof(TokenFilter))] public Task
Login(string name, string pwd) { string token = null; if (name == "admin" && pwd == "123456") token = JWTHelper.Default.CreateToken(name, "admin"); return token.ToTask(); } }
以上是对应的服务实现,但这个服务多了个TokenFilter
属性;这个属性是一个过虑器用于验证请求的,Login
方法就移走了这个验证过虑器。接下来看来下这个属性的代码:
public class TokenFilter : ActionFilterAttribute { public override bool Executing(EventCenter center, EventActionHandler handler, IEventInput input, IEventOutput output) { string token = null; input.Properties?.TryGetValue("token", out token); var user = JWTHelper.Default.GetUserInfo(token); if (user!=null) { return base.Executing(center, handler, input, output); } else { output.EventError = EventError.InnerError; output.Data = new object[] { "操作错误,无权操作相关资源!" }; return false; } } }
过虑器逻辑比较简单就是获取请求头的token
属性是否有效,如果有则通过请求没有则拒绝请求。接下来看一下WPF
的使用代码:
private IDataService dataService; private XRPCClient XRPCClient; private void Window_Loaded(object sender, RoutedEventArgs e) { XRPCClient = new XRPCClient("localhost", 9090); XRPCClient.Options.ParameterFormater = new JsonPacket(); dataService = XRPCClient.Create(); } private async void CmdSearch_Click(object sender, RoutedEventArgs e) { try { var data = await dataService.List(); lstEmployees.ItemsSource = data; } catch (Exception e_) { MessageBox.Show(e_.Message); } } private async void CmdLogin_Click(object sender, RoutedEventArgs e) { try { var token = await dataService.Login(txtName.Text, txtPwd.Text); txtToken.Content = token; ((IHeader)dataService).Header["token"] = token; } catch(Exception e_) { MessageBox.Show(e_.Message); } }
代码其实很简单,在窗体构建的时候创建一个XRPCClient
并创建对应的接口实例;在这里这里主要是关心token
的传递,因为接口上并没有方法可以这样做;其实所有代理接口都实现了一个IHeader
接口,只需要做一个显式的转换并在Header
上设置对应名称的值即可.
安全的通讯在服务交互中是必不可少的,XRPC
通过支持ssl
来解决这一问题;由于这功能是BeetleX
的基础核心,所以组件不需要太过于关注只需要简单配置一下证书即可:
static void Main(string[] args) { var builder = new HostBuilder() .ConfigureServices((hostContext, services) => { services.UseXRPC(s => { JWTHelper.Init(); s.ServerOptions.LogLevel = BeetleX.EventArgs.LogType.Trace; s.ServerOptions.DefaultListen.Port = 9090; s.ServerOptions.DefaultListen.SSL = true; s.ServerOptions.DefaultListen.CertificateFile = "test.pfx"; s.ServerOptions.DefaultListen.CertificatePassword = "123456"; s.RPCOptions.ParameterFormater = new JsonPacket();//default messagepack }, typeof(Program).Assembly); }); builder.Build().Run(); }
只要在服务中配置好证书和对应的密码即可,服务在启动的时候会看到具体的情况:
服务启用ssl
后,客户端在创建XRPCClient
指定sslServiceName
即可,代码如下:
XRPCClient = new XRPCClient("localhost", 9090, "test"); XRPCClient.CertificateValidationCallback = (s, certificate, chain, sslPolicyErrors) => true; XRPCClient.Options.ParameterFormater = new JsonPacket(); dataService = XRPCClient.Create();
当无法确定sslServiceName
的时候需要添加CertificateValidationCallback
委托自己定义验证返回的结果。