ApiClient搭配ApiServer构成RPC框架。

Nuget包:NewLife.Remoting

源码地址:https://github.com/NewLifeX/NewLife.Remoting/blob/master/NewLife.Remoting/ApiClient.cs

建议提前阅读《RPC通信ApiServer》

快速入门

新建控制台项目,写入以下代码:

using NewLife.Data;
using NewLife.Log;
using NewLife.Net;
using NewLife.Remoting;
using System;
using System.Linq;
using System.Net;

namespace NewLife.RPC
{
    class Program
    {
        static void Main(string[] args)
        {
            XTrace.UseConsole();

            var netUri = new NetUri(NetType.Tcp, IPAddress.Any, 5001);
            using var server = new ApiServer(netUri)
            {
                Log = XTrace.Log,
                EncoderLog = XTrace.Log,
                ShowError = true
            };
            server.Register<BigController>();
            server.Start();

            ClientTest();

            Console.ReadKey();
        }

        static void ClientTest()
        {
            var client = new MyClient("tcp://127.0.0.1:5001")
            {
                Log = XTrace.Log,
                EncoderLog = XTrace.Log
            };

            client.Received += (s, e) =>
            {
                XTrace.WriteLine("通知:{0} 参数:{1}", e.ApiMessage.Action, e.ApiMessage.Data.ToStr());
            };
            
            var rs = client.Invoke<Int32>("Big/Sum", new { a = 123, b = 456 });
            XTrace.WriteLine("{0}+{1}={2}", 123, 456, rs);
        }

        class MyClient : ApiClient
        {
            public MyClient(String urls) : base(urls) { }
        }

        class BigController : IApi
        {
            public IApiSession Session { get; set; }

            public int Sum(int a, int b)
            {
                Task.Run(async () =>
                {
                    await Task.Delay(1000);

                    Session.InvokeOneWay("test", new { name = "Stone", company = "NewLife" }, 3);
                });

                return a + b;
            }
            public string ToUpper(string str) => str.ToUpper();

            public Packet Test(Packet pk)
            {
                var buf = pk.ReadBytes().Select(e => (Byte)(e ^ 'x')).ToArray();

                return buf;
            }
        }
    }
}

这里的重点是客户端调用:client.Invoke<Int32>("Big/Sum", new { a = 123, b = 456 })

新生命RPC框架的客户端调用,只有一个 Invoke (异步InvokeAsync),指定接口名并传入参数对象即可。这就是客户端的全部,没有描述文件生成代码,也不需要客户端服务端共用接口文件!

当然,只要你喜欢,可以搞一套接口,让客户端和服务端控制器都实现。这么做,比较麻烦的地方在于修改接口时需要同时改几个地方。客户端服务端还可以共享出入参模型类,这也是常见做法。由于默认采用Json序列化,因此两头可以使用各自的模型类,只要字段名对得上即可,完全避免了客户端服务端升级版本不匹配所带来的各种问题。


下行通知

修改前面的示例,加上服务端主动下发代码:

class BigController : IApi
{
    public IApiSession Session { get; set; }

    public int Sum(int a, int b)
    {
        Task.Run(async () =>
        {
            await Task.Delay(1000);

            Session.InvokeOneWay("test", new { name = "Stone", company = "NewLife" }, 3);
        });

        return a + b;
    }
}

在Sum接口内,调用了 Session.InvokeOneWay。客户端也需要修改一下:

static void ClientTest()
{
    var client = new MyClient("tcp://127.0.0.1:5001")
    {
        Log = XTrace.Log,
        EncoderLog = XTrace.Log
    };

    var rs = client.Invoke<Int32>("Big/Sum", new { a = 123, b = 456 });
    XTrace.WriteLine("{0}+{1}={2}", 123, 456, rs);
}

class MyClient : ApiClient
{
    public MyClient(String urls) : base(urls) { }
    protected override void OnReceive(IMessage message)
    {
        if (Encoder.Decode(message, out var action, out _, out var args))
        {
            XTrace.WriteLine("通知:{0} 参数:{1}", action, args.ToStr());
        }
    }
}

MyClient类继承自ApiClient,重写了 OnReceive 方法,所有未经处理的消息,都将进入这里。使用编码器解码后即可得到想要的数据。