.Net Core基于Emit动态代理从零实现RPC 初级实现

上篇文章,我们已经能从零实现动态代理了,其实就是类的代理,这节课,我们就基于动态代理技术,实现RPC功能。

概念之类的上节文章,也讲了。如果有不懂的,可以看上节文章。

废话不多说,开搞。.

这节文章,代码偏多,就看代码就成了。

内容还是偏多,这节也算是代码级简单实现,后面还会有一节,搞完整案例的。

实现RPC的原理

描述一下,RPC的实现过程。

假设服务端 有一个服务,对外提供一个地址,假设是 127.0.0.1:80

然后,客户端要调用服务端的方法,那么,连接服务端,这是必须的,但是,服务端有什么方法呢。

服务端要公开它自己的类接口信息。

如下:

    public interface IDemo
    {
        void Say();
    }

那么,客户端要调用服务端,直接一个接口,客户端,也不会用啊。

就需要客户端自己new一个代理服务类的对象出来。

    /// <summary>
    /// 实现服务端的类接口
    /// </summary>
    public class Demo : IDemo
    {
        public void Say()
        {
        }
    }

那么,如何调用服务端的方法呢或者说,如何与服务端通信呢。

这里就需要一个代理对象了。这个对象把客户端需要调用的类,方法,参数,传给服务端,服务端处理后,返回客户端,然后,客户端就收到了相关信息。

方法就变成了下面这个样子

    /// <summary>
    /// 具体的类
    /// </summary>
    public class Demo : IDemo
    {
        ServerProxy serverProxy = new ServerProxy();
        public void Say()
        {
            var result = serverProxy.Invok(nameof(Demo), nameof(Demo.Say), null);
            return;
        }
    }
    public class ServerProxy
    {
        public object Invok(string classname, string method, string args)
        {
            return null;
        }
    }

所以,RPC的实现,就是构造一个服务端的接口的实现,并发送相应接口方法的参数信息,通过,一个内置的代理服务对象通讯。

基于上述原理的实现

服务端接口

尽量类型包含足够多,但是,没有包含泛型,有想法的同学可以多尝试一下。

    public interface IDemo
    {
        void Say();
        string Say(string msg);
        int Say(string a, int b, List<string> c, Kind kind);
        string Say(int b, List<string> c, Kind kind);
 
        List<string> Test { get; set; }
    }
    public enum Kind
    {
        a,
        b
    }

DnamicInterfaceProxy.cs

核心代理类的实现

    public class DnamicInterfaceProxy
    {
        private static readonly string DllName;
        private static ModuleBuilder ModuleBuilder = null;
        private static AssemblyBuilder AssemblyBuilder = null;

        private static readonly ConcurrentDictionary<Type, Type> Maps = new ConcurrentDictionary<Type, Type>();
        static DnamicInterfaceProxy()
        {
            var assemblyName = new AssemblyName(nameof(DnamicInterfaceProxy));
            DllName = assemblyName.Name + ".dll";
            AssemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
            ModuleBuilder = AssemblyBuilder.DefineDynamicModule(DllName);
        }
        public static T Resolve<T>() where T : class
        {
            var hanlder = new DefaultInvocationHandler<T>();
            var interfaceType = typeof(T);
            if (interfaceType?.IsInterface != true)
            {
                throw new ArgumentException("interfaceType");
            }
            Maps.TryGetValue(interfaceType, out Type newType);
            if (newType == null)
            {
                newType = CreateType(interfaceType);
                Maps.TryAdd(interfaceType, newType);
            }
            return (T)Activator.CreateInstance(newType, hanlder);
        }
        public static void Save()
        {
            AssemblyBuilder.Save(DllName);
        }
        private static Type CreateType(Type interfaceType)
        {
            var tb = ModuleBuilder.DefineType(string.Format("{0}.{1}", typeof(DnamicInterfaceProxy).FullName, interfaceType.Name));
            tb.AddInterfaceImplementation(interfaceType);

            var fb = tb.DefineField("_handler", typeof(InvocationHandler), FieldAttributes.Private);

            CreateConstructor(tb, fb);
            CreateMethods(interfaceType, tb, fb);
            CreateProperties(interfaceType, tb, fb);

            return tb.CreateType();
        }
        private static void CreateConstructor(TypeBuilder tb, FieldBuilder fb)
        {
            var ctor = tb.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[] { typeof(InvocationHandler) });
            var il = ctor.GetILGenerator();

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldarg_1);
            il.Emit(OpCodes.Stfld, fb);
            il.Emit(OpCodes.Ret);
        }

        private static void CreateMethods(Type interfaceType, TypeBuilder tb, FieldBuilder fb)
        {
            foreach (MethodInfo met in interfaceType.GetMethods())
            {
                CreateMethod(met, tb, fb);
            }
        }
        private static MethodBuilder CreateMethod(MethodInfo met, TypeBuilder tb, FieldBuilder fb)
        {
            var args = met.GetParameters();
            var mb = tb.DefineMethod(met.Name, MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.Virtual | MethodAttributes.Final | MethodAttributes.HideBySig,
                met.CallingConvention, met.ReturnType, args.Select(t => t.ParameterType).ToArray());
            var il = mb.GetILGenerator();
            il.DeclareLocal(typeof(object[]));

            if (met.ReturnType != typeof(void))
            {
                il.DeclareLocal(met.ReturnType);
            }

            il.Emit(OpCodes.Nop);
            il.Emit(OpCodes.Ldc_I4, args.Length);
            il.Emit(OpCodes.Newarr, typeof(object));
            il.Emit(OpCodes.Stloc_0);

            for (int i = 0; i < args.Length; i++)
            {
                il.Emit(OpCodes.Ldloc_0);
                il.Emit(OpCodes.Ldc_I4, i);
                il.Emit(OpCodes.Ldarg, 1 + i);
                var type = args[i].ParameterType;
                if (type.IsValueType)
                {
                    il.Emit(OpCodes.Box, type);
                }
                il.Emit(OpCodes.Stelem_Ref);
            }

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldfld, fb);

            il.Emit(OpCodes.Ldarg_0);
            il.Emit(OpCodes.Ldc_I4, met.MetadataToken);
            il.Emit(OpCodes.Ldstr, met.DeclaringType?.FullName + "+" + met.Name);
            il.Emit(OpCodes.Ldloc_0);
            il.Emit(OpCodes.Call, typeof(InvocationHandler).GetMethod(nameof(InvocationHandler.InvokeMember), BindingFlags.Instance | BindingFlags.Public));

            if (met.ReturnType == typeof(void))
            {
                il.Emit(OpCodes.Pop);
            }
            else
            {
                il.Emit(met.ReturnType.IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, met.ReturnType);
                il.Emit(OpCodes.Stloc_1);
                il.Emit(OpCodes.Ldloc_1);
            }
            il.Emit(OpCodes.Ret);

            return mb;
        }
        private static void CreateProperties(Type interfaceType, TypeBuilder tb, FieldBuilder fb)
        {
            foreach (var prop in interfaceType.GetProperties())
            {
                var pb = tb.DefineProperty(prop.Name, PropertyAttributes.SpecialName, prop.PropertyType, Type.EmptyTypes);
                var met = prop.GetGetMethod();
                if (met != null)
                {
                    var mb = CreateMethod(met, tb, fb);
                    pb.SetGetMethod(mb);
                }
                met = prop.GetSetMethod();
                if (met != null)
                {
                    var mb = CreateMethod(met, tb, fb);
                    pb.SetSetMethod(mb);
                }
            }
        }
    }

代理对象 InvocationHandler.cs

一个简单的实现,只做打印内容。实际业务中,它会发送给服务端执行后,并返回到客户端来。其实,就是套壳。如果能理解的话。

    /// <summary>
    /// 内置代理对象
    /// </summary>
    public interface InvocationHandler
    {
        object InvokeMember(object obj, int rid, string name, params object[] args);
    }
    public class DefaultInvocationHandler<T> : InvocationHandler
    {
        public object InvokeMember(object sender, int methodId, string name, params object[] args)
        {
            //服务端调用的时候会用
            var met = (MethodInfo)typeof(T).Module.ResolveMethod(methodId);
            string[] names = name.Split('+');
            var NameSpace = names[0];
            var Method = names[1];
            var Parameters = new List<object>(args);
            Console.WriteLine($"{NameSpace}.{Method}({string.Join(",", Parameters)})");
            return null;
        }
    }

测试端

这是基于.Net Fw写的,好处是能保存dll到本地,有助于查看结构信息。

        static void Main(string[] args)
        {
            var demo = DnamicInterfaceProxy.Resolve<IDemo>();
            demo.Say();
            demo.Say("123");
            demo.Say(5, new List<string>() { "1", "2", "3" }, Kind.a);
            demo.Say("demo", 6, new List<string>() { "6" }, Kind.b);
            demo.Test = new List<string>() { "11", "12" };
            var b = demo.Test;
            DnamicInterfaceProxy.Save();
            Console.ReadLine();
        }

结果如下

.Net Core基于Emit动态代理从零实现RPC 初级实现

从结果看,我们把相应的信息都获取到了。

我们可以先看反射生成的是不是我们需要的DLL结构

反射DLL

// Token: 0x02000002 RID: 2
    internal class IDemo : IDemo
    {
        // Token: 0x06000001 RID: 1 RVA: 0x00002050 File Offset: 0x00000250
        public IDemo(InvocationHandler A_1)
        {
        }

        // Token: 0x06000002 RID: 2 RVA: 0x00002064 File Offset: 0x00000264
        public void Say()
        {
            object[] args = new object[0];
            this._handler.InvokeMember(this, 100663311, "RPCDemo.IDemo+Say", args);
        }

        // Token: 0x06000003 RID: 3 RVA: 0x00002098 File Offset: 0x00000298
        public string Say(string A_1)
        {
            object[] args = new object[]
            {
                A_1
            };
            return (string)this._handler.InvokeMember(this, 100663312, "RPCDemo.IDemo+Say", args);
        }

        // Token: 0x06000004 RID: 4 RVA: 0x000020DC File Offset: 0x000002DC
        public int Say(string A_1, int A_2, List<string> A_3, Kind A_4)
        {
            object[] args = new object[]
            {
                A_1,
                A_2,
                A_3,
                A_4
            };
            return (int)this._handler.InvokeMember(this, 100663313, "RPCDemo.IDemo+Say", args);
        }

        // Token: 0x06000005 RID: 5 RVA: 0x00002154 File Offset: 0x00000354
        public string Say(int A_1, List<string> A_2, Kind A_3)
        {
            object[] args = new object[]
            {
                A_1,
                A_2,
                A_3
            };
            return (string)this._handler.InvokeMember(this, 100663314, "RPCDemo.IDemo+Say", args);
        }

        // Token: 0x17000001 RID: 1
        // (get) Token: 0x06000006 RID: 6 RVA: 0x000021BC File Offset: 0x000003BC
        // (set) Token: 0x06000007 RID: 7 RVA: 0x000021F4 File Offset: 0x000003F4
        public List<string> Test
        {
            get
            {
                object[] args = new object[0];
                return (List<string>)this._handler.InvokeMember(this, 100663315, "RPCDemo.IDemo+get_Test", args);
            }
            set
            {
                object[] args = new object[]
                {
                    value
                };
                this._handler.InvokeMember(this, 100663316, "RPCDemo.IDemo+set_Test", args);
            }
        }

        // Token: 0x06000008 RID: 8 RVA: 0x00002234 File Offset: 0x00000434
        public List<string> get_Test()
        {
            object[] args = new object[0];
            return (List<string>)this._handler.InvokeMember(this, 100663315, "RPCDemo.IDemo+get_Test", args);
        }

        // Token: 0x06000009 RID: 9 RVA: 0x0000226C File Offset: 0x0000046C
        public void set_Test(List<string> A_1)
        {
            object[] args = new object[]
            {
                A_1
            };
            this._handler.InvokeMember(this, 100663316, "RPCDemo.IDemo+set_Test", args);
        }

        // Token: 0x04000001 RID: 1
        private InvocationHandler _handler = A_1;
    }

看不清楚,可以看图

.Net Core基于Emit动态代理从零实现RPC 初级实现
.Net Core基于Emit动态代理从零实现RPC 初级实现

也是完全实现了的,当然,也有很多的细节,想研究的朋友可以深入研究一下。

总结

从完整性上来讲,此项目已经非常完整了。可以根据这项目开发基于自己的RPC项目了。

从 DefaultInvocationHandler 来进行实现。

下一节,我也会搞出来自己的实现,大家一起进步,记得关注我呦!,你的关注,就是我的动力。

代码地址

https://github.com/kesshei/RPCDemo.git

https://gitee.com/kesshei/RPCDemo.git