上篇文章,我们已经能从零实现动态代理了,其实就是类的代理,这节课,我们就基于动态代理技术,实现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 来进行实现。
下一节,我也会搞出来自己的实现,大家一起进步,记得关注我呦!,你的关注,就是我的动力。
代码地址
https://github.com/kesshei/RPCDemo.git
https://gitee.com/kesshei/RPCDemo.git