BeetleX之XRPC使用详解

XRPC 是基于 BeetleX 扩展一个远程接口调用组件,它提供基于接口的方式来实现远程服务调用,在应用上非常简便。组件提供 .NETCore2.1.NETStandard2.0 的client版本,因此即使在 winfromwpf 也可以使用该组件进行服务调用处理。接下来详细讲解一下 XRPC 使用,从简单的 hellowpf 调用服务和安全可靠的 ssl 通讯等。

引用组件

组件提供了两个版本 BeetleX.XRPC 对应 .NETCore2.1 它同时提供服务和客户端调用功能, BeetleX.XRPC.Clients 是对应 Standard2.0 客户端版本,专门针对桌面应用调用而开发。除了这两个组件外还提供了 BeetleX.XRPC.Hosting ,这个组件专门为 XRPC 服务提供以 Hosting 方式运行的支持,如果你想使用 DI 那也可以通过这个组件实现。

Hello

很多程序的开始都是以 Hello 来展示使用,接下来就使用组件构建一个 Hello 的通讯服务。组件的所有服务都需要通过接口来描述,所以在制定服务前需要用接口来描述一下服务需求:

public interface IHello
    {
        Task<string> Hello(string name);
    }

以上是一个 Hello 服务接口的定义(接口定义要求是所有方法都必须以 Task<T>Task 作为返回值)。服务实现

[Service(typeof(IHello))]
    public class HelloImpl : IHello
    {
        public Task<string> Hello(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<IHello>();
            while (true)
            {
                Console.Write("Enter you name:");
                var name = Console.ReadLine();
                var result = await hello.Hello(name);
                Console.WriteLine(result);
            }

只需要指定 XRPCClient 对应的服务地址和端口,并创建接口即可调用。 XRPCClient 和它创建的接口都是线程安全的,因此只需要定义一个即可在并发中使用。

参数编码

组件提供 jsonmessagepack 作为参数传递的编码, messagepack 是默认编码使这种编码序列化对象可以达到非常好的效率,但这种编码需要对类的属性进行标记使用也相对麻烦;如果对效率要求不高不想对类进行属性标记可以设置成 Json .如果想实现自己的编码方式可以通过实现以下接口:

public interface IParameterFormater
    {
        void Encode(Options rpcOption, object data, PipeStream stream);

        object Decode(Options rpcOption, Type type, ArraySegment<byte> data);
    }

创建 Actor 模式对象

Actor 是一种非常高效的业务处理模型,每个实例有着独立线程资源,其行所有为是串行操作,所以它这种线程独立性和无锁机制非常适合高并发业务处理; XPRC 支持远程 Actor 创建,并在服务端维持其独立性,在多客户端同时调用同一 Actor 行为时服务端会保证其自有的特性运行。

public interface IAmount
    {
        Task Income(int value);
        Task Pay(int value);
        Task<int> GetValue();
    }

以上是一个简单的数量增接口,实现的服务如下:

[Service(typeof(IAmount))]
    public class AmountImpl : IAmount
    {

        private int mAmount;

        public Task<int> GetValue()
        {
            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<IAmount>("henry");
            ken = client.Create<IAmount>("ken");

以上是针对 IAmount 创建两个实例,分别是: henryken .服务端会根据请求的标识在服务端维护各自的 actor 实例。多客户端同时创建相同名称的 actor 实例怎办?即是多客户端同时创建同一名称的 actor 和并发调用,服务端都可保证 actor 实例的唯一性(实际应用需要涉及到 actor 的状态,信息持久化等,这些就不在这里讨论; XRPC 的这一功能则由https://github.com/IKende/EventNext 提供)。

在WPF中调用

有时候需要在 winfromwpf 中调用服务,这个时候就需要通过 BeetleX.XRPC.Clients 来实现调用;它所提供的功能和 BeetleX.XRPC 内置的客户端功能是一样的。接下来做一个简单的数据查询,不过这个示例为了符合客户端的需求还针对方法添加了 JWT 验证的功能。

public interface IDataService
    {
        Task<string> Login(string name, string pwd);

        Task<List<Employee>> List();
    }

以上是一个简单的数据查询接口,里面添加了一个登陆方法.

[Service(typeof(IDataService))]
    [TokenFilter]
    public class DataServiceImpl : IDataService
    {
        public Task<List<Employee>> List()
        {
            return DataHelper.Defalut.Employees.ToTask();
        }

        [SkipActionFilter(typeof(TokenFilter))]
        public Task<string> 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<IDataService>();
        }

        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 上设置对应名称的值即可.

ssl的支持

安全的通讯在服务交互中是必不可少的, 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<IDataService>();

当无法确定 sslServiceName 的时候需要添加 CertificateValidationCallback 委托自己定义验证返回的结果。

对象注入

由于服务对象的创建是由组件托管的,所以很多时候需要对接口服务添加不同的参数和属性方便功能集成。用户可以通过以下事件来服务类创建:

XRPCServer.EventCenter.ServiceInstance

其实组件提供了 BeetleX.XRPC.Hosting 扩展来成这功能,通过这个扩展在服务启动时进行注册,代码如下:

var builder = new HostBuilder()
.ConfigureServices((hostContext, services) =>
{
     services.AddSingleton(new User { Name = "BeetleX" });
     services.UseXRPC(s =>
     {
                    //...
     },
     typeof(Program).Assembly);
});
builder.Build().Run();

通过 services 可以给DI容器添注入需要的对象,当容器注册后就可以在服务类的构建函数中定义需要的参数:

[Service(typeof(IHello))]
    public class HelloImpl : IHello
    {
        public HelloImpl(BeetleX.XRPC.XRPCServer server, User user)
        {
            mServer = server;
            mUser = user;
        }
   }

以上讲述了 XRPC 的使用情况,详细代码可以访问 https://github.com/IKende/BeetleX-Samples

我来评几句
登录后评论

已发表评论数()

相关站点

热门文章