.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();
}

结果如下

在这里插入图片描述

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

我们可以先看反射生成的是不是我们需要的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;
	}

看不清楚,可以看图
在这里插入图片描述
在这里插入图片描述

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

总结

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

从 DefaultInvocationHandler 来进行实现。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值