自行实现 dotnet core rpc
前言
小李:“胖子,上头叫你对接我的数据好了没有?”
胖子:“那是你的事,你都不提供数据源,我咋接?”
小李:“你想要什么样的数据源?”
胖子:“我想要一个调用简单点的!”
小李:“我这个数据源是在linux平台使用docker封装发布的,webapi的怎么样?”
胖子:“也行,这项目工期快用完了,你得提供api封装sdk,另外我这边对性能有要求的!”
小李:“webapi多好,基于json各个平台都能对接,性能还不错的!”
胖子:“我只关心我的业务,不是我的业务代码,多一行我都不想码,不然没按时完成算你的!另外用webapi到时候请求量一大,到时候端口用完了,连接不了这锅也得你背!”
小李:“我@##¥%*#¥@#&##@……”
面对胖子这些说辞,小李心里面虽然一万只草泥马在奔腾,但是项目还是要完成是不?另外胖子说的也不无道理!小李作为一个在C#下侵淫多年老鸟,很快想出一个办法——rpc!首先当然是选wcf,这个巨硬的企业级产品在快速开发上除了配置上坑爹了一点,针对客户端的对接真的非常快。小李仔细一研究wcf service 发现目前在linux下玩不了,心里面又是了一阵@##¥%*#¥@#&##@……
胖子:“小李纠结啥,要不就弄个三方的搞一下算了,就算出事了,你说不定都已经离职了,怕啥……”
看着胖子一脸猥琐的表情,小李那是一个气啊,就怪自已平时牛逼吹上天,这时候怎么好怂呢,一咬牙:“你放心,误不了你的事!”。小李一边回复,心里面开始盘算着自行实现一个功能简易,性能高效,使用简单的rpc了。
上面小李与胖子的场景,在开发的时候也是经典案例,回到正题来:本人认为rpc主要是:调用方法及参数序列化、socket传输、调用方法及参数反序列化、映射到本地并采用与请求相同流程回应客户端的一套方案。其中关键点简单分析主要有:序列化与反序列化、高性能tcp、远程方法反转、客户端代码生成四个方面;tcp还是使用iocp好了,其他接着一一分析。
序列化与反序列化
序列化与反序列化这个选二进制一般比json的好,ms版的BinaryFormatter 通用性强,但是他的性能、model的标记写法等估计又要被喷了;找到Expression序列化,结果还是走的类似于soap xml这一套,想想算了:本地方法调用都是纳秒级的,io都是毫秒级别的,socket的一次就传这么传这么大一堆,就算局域网也伤不起呀,想轻量化提升性能都难,自行实现一个简单的好了。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.RPC.Serialize
*文件名: SerializeUtil
*版本号: V1.0.0.0
*唯一标识:9e919430-465d-49a3-91be-b36ac682e283
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/5/22 13:17:36
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/5/22 13:17:36
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.RPC.Model;
using System;
using System.Collections.Generic;
using System.Text; namespace SAEA.RPC.Serialize
{
/// <summary>
/// rpc参数序列化处理
/// </summary>
public class ParamsSerializeUtil
{
/// <summary>
/// len+data
/// </summary>
/// <param name="param"></param>
/// <returns></returns>
public static byte[] Serialize(object param)
{
List<byte> datas = new List<byte>(); var len = ;
byte[] data = null; if (param == null)
{
len = ;
}
else
{
if (param is string)
{
data = Encoding.UTF8.GetBytes((string)param);
}
else if (param is byte)
{
data = new byte[] { (byte)param };
}
else if (param is bool)
{
data = BitConverter.GetBytes((bool)param);
}
else if (param is short)
{
data = BitConverter.GetBytes((short)param);
}
else if (param is int)
{
data = BitConverter.GetBytes((int)param);
}
else if (param is long)
{
data = BitConverter.GetBytes((long)param);
}
else if (param is float)
{
data = BitConverter.GetBytes((float)param);
}
else if (param is double)
{
data = BitConverter.GetBytes((double)param);
}
else if (param is DateTime)
{
var str = "wl" + ((DateTime)param).Ticks;
data = Encoding.UTF8.GetBytes(str);
}
else if (param is byte[])
{
data = (byte[])param;
}
else
{
var type = param.GetType(); if (type.IsGenericType || type.IsArray)
{
data = SerializeList((System.Collections.IEnumerable)param);
}
else if (type.IsGenericTypeDefinition)
{
data = SerializeDic((System.Collections.IDictionary)param);
}
else if (type.IsClass)
{
var ps = type.GetProperties(); if (ps != null && ps.Length > )
{
List<object> clist = new List<object>(); foreach (var p in ps)
{
clist.Add(p.GetValue(param));
}
data = Serialize(clist.ToArray());
}
}
}
len = data.Length;
}
datas.AddRange(BitConverter.GetBytes(len));
if (len > )
{
datas.AddRange(data);
}
return datas.Count == ? null : datas.ToArray();
} private static byte[] SerializeList(System.Collections.IEnumerable param)
{
List<byte> list = new List<byte>(); if (param != null)
{
List<byte> slist = new List<byte>(); foreach (var item in param)
{
var type = item.GetType(); var ps = type.GetProperties();
if (ps != null && ps.Length > )
{
List<object> clist = new List<object>();
foreach (var p in ps)
{
clist.Add(p.GetValue(item));
} var clen = ; var cdata = Serialize(clist.ToArray()); if (cdata != null)
{
clen = cdata.Length;
} slist.AddRange(BitConverter.GetBytes(clen));
slist.AddRange(cdata);
}
} var len = ; if (slist.Count > )
{
len = slist.Count;
}
list.AddRange(BitConverter.GetBytes(len));
list.AddRange(slist.ToArray());
}
return list.ToArray();
} private static byte[] SerializeDic(System.Collections.IDictionary param)
{
List<byte> list = new List<byte>(); if (param != null && param.Count > )
{
foreach (KeyValuePair item in param)
{
var type = item.GetType();
var ps = type.GetProperties();
if (ps != null && ps.Length > )
{
List<object> clist = new List<object>();
foreach (var p in ps)
{
clist.Add(p.GetValue(item));
}
var clen = ; var cdata = Serialize(clist.ToArray()); if (cdata != null)
{
clen = cdata.Length;
} list.AddRange(BitConverter.GetBytes(clen));
list.AddRange(cdata);
}
}
}
return list.ToArray();
} /// <summary>
/// len+data
/// </summary>
/// <param name="params"></param>
/// <returns></returns>
public static byte[] Serialize(params object[] @params)
{
List<byte> datas = new List<byte>(); if (@params != null)
{
foreach (var param in @params)
{
datas.AddRange(Serialize(param));
}
} return datas.Count == ? null : datas.ToArray();
} /// <summary>
/// 反序列化
/// </summary>
/// <param name="types"></param>
/// <param name="datas"></param>
/// <returns></returns>
public static object[] Deserialize(Type[] types, byte[] datas)
{
List<object> list = new List<object>(); var len = ; byte[] data = null; int offset = ; for (int i = ; i < types.Length; i++)
{
list.Add(Deserialize(types[i], datas, ref offset));
} return list.ToArray();
} /// <summary>
/// 反序列化
/// </summary>
/// <param name="type"></param>
/// <param name="datas"></param>
/// <param name="offset"></param>
/// <returns></returns>
public static object Deserialize(Type type, byte[] datas, ref int offset)
{
dynamic obj = null; var len = ; byte[] data = null; len = BitConverter.ToInt32(datas, offset);
offset += ;
if (len > )
{
data = new byte[len];
Buffer.BlockCopy(datas, offset, data, , len);
offset += len; if (type == typeof(string))
{
obj = Encoding.UTF8.GetString(data);
}
else if (type == typeof(byte))
{
obj = (data);
}
else if (type == typeof(bool))
{
obj = (BitConverter.ToBoolean(data, ));
}
else if (type == typeof(short))
{
obj = (BitConverter.ToInt16(data, ));
}
else if (type == typeof(int))
{
obj = (BitConverter.ToInt32(data, ));
}
else if (type == typeof(long))
{
obj = (BitConverter.ToInt64(data, ));
}
else if (type == typeof(float))
{
obj = (BitConverter.ToSingle(data, ));
}
else if (type == typeof(double))
{
obj = (BitConverter.ToDouble(data, ));
}
else if (type == typeof(decimal))
{
obj = (BitConverter.ToDouble(data, ));
}
else if (type == typeof(DateTime))
{
var dstr = Encoding.UTF8.GetString(data);
var ticks = long.Parse(dstr.Substring());
obj = (new DateTime(ticks));
}
else if (type == typeof(byte[]))
{
obj = (byte[])data;
}
else if (type.IsGenericType)
{
obj = DeserializeList(type, data);
}
else if (type.IsArray)
{
obj = DeserializeArray(type, data);
}
else if (type.IsGenericTypeDefinition)
{
obj = DeserializeDic(type, data);
}
else if (type.IsClass)
{
var instance = Activator.CreateInstance(type); var ts = new List<Type>(); var ps = type.GetProperties(); if (ps != null)
{
foreach (var p in ps)
{
ts.Add(p.PropertyType);
}
var vas = Deserialize(ts.ToArray(), data); for (int j = ; j < ps.Length; j++)
{
try
{
if (!ps[j].PropertyType.IsGenericType)
{
ps[j].SetValue(instance, Convert.ChangeType(vas[j], ps[j].PropertyType), null);
}
else
{
Type genericTypeDefinition = ps[j].PropertyType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(Nullable<>))
{
ps[j].SetValue(instance, Convert.ChangeType(vas[j], Nullable.GetUnderlyingType(ps[j].PropertyType)), null);
}
else
{
//List<T>问题
ps[j].SetValue(instance, Convert.ChangeType(vas[j], ps[j].PropertyType), null);
}
}
}
catch (Exception ex)
{
Console.WriteLine("反序列化不支持的类型:" + ex.Message);
}
}
}
obj = (instance);
}
else
{
throw new RPCPamarsException("ParamsSerializeUtil.Deserialize 未定义的类型:" + type.ToString());
} }
return obj;
} private static object DeserializeList(Type type, byte[] datas)
{
List<object> result = new List<object>();
var stype = type.GenericTypeArguments[]; var len = ;
var offset = ;
//容器大小
len = BitConverter.ToInt32(datas, offset);
offset += ;
byte[] cdata = new byte[len];
Buffer.BlockCopy(datas, offset, cdata, , len);
offset += len; //子项内容
var slen = ;
var soffset = ;
while (soffset < len)
{
slen = BitConverter.ToInt32(cdata, soffset);
var sdata = new byte[slen + ];
Buffer.BlockCopy(cdata, soffset, sdata, , slen + );
soffset += slen + ; if (slen > )
{
int lloffset = ;
var sobj = Deserialize(stype, sdata, ref lloffset);
if (sobj != null)
result.Add(sobj);
}
else
{
result.Add(null);
}
}
return result;
} private static object DeserializeArray(Type type, byte[] datas)
{
var obj = DeserializeList(type, datas); if (obj == null) return null; var list = (obj as List<object>); return list.ToArray();
} private static object DeserializeDic(Type type, byte[] datas)
{
dynamic obj = null; return obj;
}
}
}
实现的过程中,一般结构、类都还比较顺利,但是数组、List、Dictionary还是遇到了一些麻烦,暂时先放着,找到办法再说。真要是传这些,目前先用其他序列化成byte[]来做……
远程方法反转
远程方法反转即是将接收到的数据定位到本地的对象方法上,如果代码生成、参数使用使用泛型反序列化,理论上是可以提升一些性能的;但是一边写服务业务,一边编写定义结构文件、还一边生成服务代码,本地方法都是纳秒级、相对io的速度来讲,如果为了这点性能提升,在使用的时候估计又是一阵@##¥%*#¥@#&##@……,所以还是使用反射、拆箱吧。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.RPC.Common
*文件名: RPCInovker
*版本号: V1.0.0.0
*唯一标识:289c03b9-3910-4e15-8072-93243507689c
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/5/17 14:11:30
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/5/17 14:11:30
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.RPC.Model;
using SAEA.RPC.Net;
using SAEA.RPC.Serialize;
using SAEA.Sockets.Interface;
using System;
using System.Linq;
using System.Reflection; namespace SAEA.RPC.Common
{
/// <summary>
/// RPC将远程调用反转到本地服务
/// </summary>
public class RPCReversal
{
static object _locker = new object(); /// <summary>
/// 执行方法
/// </summary>
/// <param name="action"></param>
/// <param name="obj"></param>
/// <param name="args"></param>
/// <returns></returns>
private static object ReversalMethod(MethodInfo action, object obj, object[] args)
{
object result = null;
try
{
var @params = action.GetParameters(); if (@params != null && @params.Length > )
{
result = action.Invoke(obj, args);
}
else
{
result = action.Invoke(obj, null);
}
}
catch (Exception ex)
{
throw new RPCPamarsException($"{obj}/{action.Name},出现异常:{ex.Message}", ex);
}
return result;
} public static object Reversal(IUserToken userToken, string serviceName, string methodName, object[] inputs)
{
lock (_locker)
{
try
{
var serviceInfo = RPCMapping.Get(serviceName, methodName); if (serviceInfo == null)
{
throw new RPCNotFundException($"当前请求找不到:{serviceName}/{methodName}", null);
} var nargs = new object[] { userToken, serviceName, methodName, inputs }; if (serviceInfo.FilterAtrrs != null && serviceInfo.FilterAtrrs.Count > )
{
foreach (var arr in serviceInfo.FilterAtrrs)
{
var goOn = (bool)arr.GetType().GetMethod("OnActionExecuting").Invoke(arr, nargs.ToArray()); if (!goOn)
{
return new RPCNotFundException("当前逻辑已被拦截!", null);
}
}
} if (serviceInfo.ActionFilterAtrrs != null && serviceInfo.ActionFilterAtrrs.Count > )
{
foreach (var arr in serviceInfo.ActionFilterAtrrs)
{
var goOn = (bool)arr.GetType().GetMethod("OnActionExecuting").Invoke(arr, nargs.ToArray()); if (!goOn)
{
return new RPCNotFundException("当前逻辑已被拦截!", null);
}
}
} var result = ReversalMethod(serviceInfo.Mothd, serviceInfo.Instance, inputs); nargs = new object[] { userToken, serviceName, methodName, inputs, result }; if (serviceInfo.FilterAtrrs != null && serviceInfo.FilterAtrrs.Count > )
{
foreach (var arr in serviceInfo.FilterAtrrs)
{
arr.GetType().GetMethod("OnActionExecuted").Invoke(arr, nargs);
}
} if (serviceInfo.ActionFilterAtrrs != null && serviceInfo.ActionFilterAtrrs.Count > )
{
foreach (var arr in serviceInfo.FilterAtrrs)
{
arr.GetType().GetMethod("OnActionExecuted").Invoke(arr, nargs);
}
}
return result;
}
catch (Exception ex)
{
if (ex.Message.Contains("找不到此rpc方法"))
{
return new RPCNotFundException("找不到此rpc方法", ex);
}
else
{
return new RPCNotFundException("找不到此rpc方法", ex);
}
}
}
} /// <summary>
/// 反转到具体的方法上
/// </summary>
/// <param name="userToken"></param>
/// <param name="msg"></param>
/// <returns></returns>
public static byte[] Reversal(IUserToken userToken, RSocketMsg msg)
{
byte[] result = null;
try
{
object[] inputs = null; if (msg.Data != null)
{
var ptypes = RPCMapping.Get(msg.ServiceName, msg.MethodName).Pamars.Values.ToArray(); inputs = ParamsSerializeUtil.Deserialize(ptypes, msg.Data);
} var r = Reversal(userToken, msg.ServiceName, msg.MethodName, inputs); if (r != null)
{
return ParamsSerializeUtil.Serialize(r);
}
}
catch (Exception ex)
{
throw new RPCPamarsException("RPCInovker.Invoke error:" + ex.Message, ex);
}
return result; }
}
}
客户端代码生成
为了方便客户使用rpc,所以有rpc相关的代码在客户端那肯定是越少越好,如果光服务端方便,客户端估计又要@##¥%*#¥@#&##@……,所以将一些rpc相关代码生成好,客户端透明调用是必须的。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.RPC.Generater
*文件名: CodeGnerater
*版本号: V1.0.0.0
*唯一标识:59ba5e2a-2fd0-444b-a260-ab68c726d7ee
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/5/17 18:30:57
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/5/17 18:30:57
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.RPC.Common;
using SAEA.RPC.Model;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text; namespace SAEA.RPC.Generater
{
/// <summary>
/// 代码生成器
/// </summary>
public static class CodeGnerater
{
static string space4 = " "; /// <summary>
/// 获取指定数量的空格
/// </summary>
/// <param name="num"></param>
/// <returns></returns>
static string GetSpace(int num = )
{
var sb = new StringBuilder(); for (int i = ; i < num; i++)
{
sb.Append(space4);
} return sb.ToString();
} /// <summary>
/// 获取变量名
/// </summary>
/// <param name="str"></param>
/// <returns></returns>
static string GetSuffixStr(string str)
{
return "_" + str.Substring(, ).ToLower() + str.Substring();
} /// <summary>
/// 生成代码头部
/// </summary>
/// <returns></returns>
static string Header(params string[] usings)
{
var sb = new StringBuilder();
sb.AppendLine("/*******");
sb.AppendLine($"*此代码为SAEA.RPCGenerater生成 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}");
sb.AppendLine("*******/" + Environment.NewLine);
sb.AppendLine("using System;");
if (usings != null)
{
foreach (var u in usings)
{
sb.AppendLine(u);
}
}
return sb.ToString();
} static string _proxyStr; static List<string> _serviceStrs = new List<string>(); static Dictionary<string, string> _modelStrs = new Dictionary<string, string>(); /// <summary>
/// 生成代理代码
/// </summary>
/// <param name="spaceName"></param>
internal static void GenerateProxy(string spaceName)
{
StringBuilder csStr = new StringBuilder();
csStr.AppendLine(Header("using SAEA.RPC.Consumer;", $"using {spaceName}.Consumer.Model;", $"using {spaceName}.Consumer.Service;"));
csStr.AppendLine($"namespace {spaceName}.Consumer");
csStr.AppendLine("{");
csStr.AppendLine($"{GetSpace(1)}public class RPCServiceProxy");
csStr.AppendLine(GetSpace() + "{"); csStr.AppendLine(GetSpace() + "ServiceConsumer _serviceConsumer;");
csStr.AppendLine(GetSpace() + "public RPCServiceProxy(string uri = \"rpc://127.0.0.1:39654\") : this(new Uri(uri)){}");
csStr.AppendLine(GetSpace() + "public RPCServiceProxy(Uri uri)");
csStr.AppendLine(GetSpace() + "{"); csStr.AppendLine(GetSpace() + "_serviceConsumer = new ServiceConsumer(uri);"); var names = RPCMapping.GetServiceNames(); if (names != null)
{
foreach (var name in names)
{
csStr.AppendLine(GetSpace() + GetSuffixStr(name) + $" = new {name}(_serviceConsumer);");
}
}
csStr.AppendLine(GetSpace() + "}"); if (names != null)
{
foreach (var name in names)
{
var suffixStr = GetSuffixStr(name); csStr.AppendLine(GetSpace() + $"{name} {suffixStr};");
csStr.AppendLine(GetSpace() + $"public {name} {name}");
csStr.AppendLine(GetSpace() + "{");
csStr.AppendLine($"{GetSpace(3)} get{{ return {suffixStr}; }}");
csStr.AppendLine(GetSpace() + "}"); var list = RPCMapping.GetAll(name);
if (list != null)
{
GenerateService(spaceName, name, list);
}
}
} csStr.AppendLine(GetSpace() + "}");
csStr.AppendLine("}");
_proxyStr = csStr.ToString();
}
/// <summary>
/// 生成调用服务代码
/// </summary>
/// <param name="spaceName"></param>
/// <param name="serviceName"></param>
/// <param name="methods"></param>
internal static void GenerateService(string spaceName, string serviceName, Dictionary<string, ServiceInfo> methods)
{
StringBuilder csStr = new StringBuilder();
csStr.AppendLine($"namespace {spaceName}.Consumer.Service");
csStr.AppendLine("{");
csStr.AppendLine($"{GetSpace(1)}public class {serviceName}");
csStr.AppendLine(GetSpace() + "{");
csStr.AppendLine(GetSpace() + "ServiceConsumer _serviceConsumer;");
csStr.AppendLine(GetSpace() + $"public {serviceName}(ServiceConsumer serviceConsumer)");
csStr.AppendLine(GetSpace() + "{");
csStr.AppendLine(GetSpace() + "_serviceConsumer = serviceConsumer;");
csStr.AppendLine(GetSpace() + "}"); foreach (var item in methods)
{
var rtype = item.Value.Mothd.ReturnType; if (rtype != null)
{
if (!_modelStrs.ContainsKey($"{spaceName}.Consumer.Model.{rtype.Name}"))
{
GenerateModel(spaceName, rtype);
}
} var argsStr = new StringBuilder(); var argsInput = new StringBuilder(); if (item.Value.Pamars != null)
{
int i = ;
foreach (var arg in item.Value.Pamars)
{
i++;
argsStr.Append(arg.Value.Name);
argsStr.Append(" ");
argsStr.Append(arg.Key);
if (i < item.Value.Pamars.Count)
argsStr.Append(", "); if (arg.Value != null && arg.Value.IsClass)
{
if (!_modelStrs.ContainsKey($"{spaceName}.Consumer.Model.{arg.Value.Name}"))
{
GenerateModel(spaceName, arg.Value);
}
} argsInput.Append(", ");
argsInput.Append(arg.Key);
}
} csStr.AppendLine(GetSpace() + $"public {rtype.Name} {item.Key}({argsStr.ToString()})");
csStr.AppendLine(GetSpace() + "{");
csStr.AppendLine(GetSpace() + $"return _serviceConsumer.RemoteCall<{rtype.Name}>(\"{serviceName}\", \"{item.Key}\"{argsInput.ToString()});");
csStr.AppendLine(GetSpace() + "}"); } csStr.AppendLine(GetSpace() + "}");
csStr.AppendLine("}");
_serviceStrs.Add(csStr.ToString());
} /// <summary>
/// 生成实体代码
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="t"></param>
/// <returns></returns>
internal static void GenerateModel(string spaceName, Type type)
{
if (!IsModel(type)) return;
StringBuilder csStr = new StringBuilder();
csStr.AppendLine($"namespace {spaceName}.Consumer.Model");
csStr.AppendLine("{");
csStr.AppendLine($"{GetSpace(1)}public class {type.Name}");
csStr.AppendLine(GetSpace() + "{");
var ps = type.GetProperties();
foreach (var p in ps)
{
csStr.AppendLine($"{GetSpace(2)}public {p.PropertyType.Name} {p.Name}");
csStr.AppendLine(GetSpace() + "{");
csStr.AppendLine(GetSpace() + "get;set;");
csStr.AppendLine(GetSpace() + "}");
}
csStr.AppendLine(GetSpace() + "}");
csStr.AppendLine("}");
_modelStrs.Add($"{spaceName}.Consumer.Model.{type.Name}", csStr.ToString());
} /// <summary>
/// 是否是实体
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
internal static bool IsModel(Type type)
{
if (type.IsArray || type.IsSealed || !type.IsClass)
{
return false;
}
return true;
} /// <summary>
/// 生成客户端C#代码文件
/// </summary>
/// <param name="folder"></param>
/// <param name="spaceName"></param>
public static void Generate(string folder, string spaceName)
{
RPCMapping.RegistAll(); GenerateProxy(spaceName); var filePath = Path.Combine(folder, "RPCServiceProxy.cs"); StringBuilder sb = new StringBuilder(); sb.AppendLine(_proxyStr); if (_serviceStrs != null && _serviceStrs.Count > )
{
foreach (var serviceStr in _serviceStrs)
{
sb.AppendLine(serviceStr);
}
} if (_modelStrs != null && _modelStrs.Count > )
{
foreach (var entry in _modelStrs)
{
sb.AppendLine(entry.Value);
}
} if (File.Exists(filePath))
File.Delete(filePath); File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);
} }
}
无论在服务端根据数据将远程调用反转本地方法、还是生成客户端代码的过程都离不开服务结构的问题。如果是根据结构文件来处理,则先要编写结构文件;服务端码农活不重事不多啊?文档没发你啊?啥锅都往这边甩……此处省略一万字。另外一种方式就是类似web mvc采用约定方式,写完服务业务代码后,再自动生成结构并缓存在内存里。
/****************************************************************************
*Copyright (c) 2018 Microsoft All Rights Reserved.
*CLR版本: 4.0.30319.42000
*机器名称:WENLI-PC
*公司名称:Microsoft
*命名空间:SAEA.RPC.Provider
*文件名: ServiceTable
*版本号: V1.0.0.0
*唯一标识:e95f1d0b-f172-49c7-b75f-67f333504260
*当前的用户域:WENLI-PC
*创建人: yswenli
*电子邮箱:wenguoli_520@qq.com
*创建时间:2018/5/16 17:46:34
*描述:
*
*=====================================================================
*修改标记
*修改时间:2018/5/16 17:46:34
*修改人: yswenli
*版本号: V1.0.0.0
*描述:
*
*****************************************************************************/
using SAEA.Commom;
using SAEA.RPC.Model;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection; namespace SAEA.RPC.Common
{
/// <summary>
/// 服务类缓存表
/// md5+ServiceInfo反射结果
/// </summary>
internal static class RPCMapping
{
static object _locker = new object(); static HashMap<string, string, ServiceInfo> _serviceMap = new HashMap<string, string, ServiceInfo>(); /// <summary>
/// 本地注册RPC服务缓存
/// </summary>
public static HashMap<string, string, ServiceInfo> ServiceMap
{
get
{
return _serviceMap;
}
} /// <summary>
/// 本地注册RPC服务
/// </summary>
/// <param name="type"></param>
public static void Regist(Type type)
{
lock (_locker)
{
var serviceName = type.Name; if (IsRPCService(type))
{
var methods = type.GetMethods(); var rms = GetRPCMehod(methods); if (rms.Count > )
{
foreach (var m in rms)
{
var serviceInfo = new ServiceInfo()
{
Type = type,
Instance = Activator.CreateInstance(type),
Mothd = m,
Pamars = m.GetParameters().ToDic()
}; List<object> iAttrs = null; //类上面的过滤
var attrs = type.GetCustomAttributes(true); if (attrs != null && attrs.Length > )
{
var classAttrs = attrs.Where(b => b.GetType().BaseType.Name == "ActionFilterAttribute").ToList(); if (classAttrs != null && classAttrs.Count > ) iAttrs = classAttrs; } serviceInfo.FilterAtrrs = iAttrs; //action上面的过滤
var actionAttrs = m.GetCustomAttributes(true); if (actionAttrs != null)
{
var filterAttrs = attrs.Where(b => b.GetType().BaseType.Name == "ActionFilterAttribute").ToList(); if (filterAttrs != null && filterAttrs.Count > ) serviceInfo.ActionFilterAtrrs = filterAttrs;
} _serviceMap.Set(serviceName, m.Name, serviceInfo);
}
}
}
}
} /// <summary>
/// 本地注册RPC服务
/// 若为空,则默认全部注册带有ServiceAttribute的服务
/// </summary>
/// <param name="types"></param>
public static void Regists(params Type[] types)
{
if (types != null)
foreach (var type in types)
{
Regist(type);
}
else
RegistAll();
}
/// <summary>
/// 全部注册带有ServiceAttribute的服务
/// </summary>
public static void RegistAll()
{
StackTrace ss = new StackTrace(true);
MethodBase mb = ss.GetFrame().GetMethod();
var space = mb.DeclaringType.Namespace;
var tt = mb.DeclaringType.Assembly.GetTypes();
Regists(tt);
} /// <summary>
/// 判断类是否是RPCService
/// </summary>
/// <param name="type"></param>
/// <returns></returns>
public static bool IsRPCService(Type type)
{
var isService = false;
var cAttrs = type.GetCustomAttributes(true);
if (cAttrs != null)
{
foreach (var cAttr in cAttrs)
{
if (cAttr is RPCServiceAttribute)
{
isService = true;
break;
}
}
}
return isService;
} /// <summary>
/// 获取RPC方法集合
/// </summary>
/// <param name="mInfos"></param>
/// <returns></returns>
public static List<MethodInfo> GetRPCMehod(MethodInfo[] mInfos)
{
List<MethodInfo> result = new List<MethodInfo>();
if (mInfos != null)
{
var isRPC = false;
foreach (var method in mInfos)
{
if (method.IsAbstract || method.IsConstructor || method.IsFamily || method.IsPrivate || method.IsStatic || method.IsVirtual)
{
break;
} isRPC = true;
var attrs = method.GetCustomAttributes(true);
if (attrs != null)
{
foreach (var attr in attrs)
{
if (attr is NoRpcAttribute)
{
isRPC = false;
break;
}
}
}
if (isRPC)
{
result.Add(method);
}
}
}
return result;
} /// <summary>
/// 转换成字典
/// </summary>
/// <param name="parameterInfos"></param>
/// <returns></returns>
public static Dictionary<string, Type> ToDic(this ParameterInfo[] parameterInfos)
{
if (parameterInfos == null) return null; Dictionary<string, Type> dic = new Dictionary<string, Type>(); foreach (var p in parameterInfos)
{
dic.Add(p.Name, p.ParameterType);
} return dic;
} /// <summary>
/// 获取缓存内容
/// </summary>
/// <param name="serviceName"></param>
/// <param name="methodName"></param>
/// <returns></returns>
public static ServiceInfo Get(string serviceName, string methodName)
{
lock (_locker)
{
return _serviceMap.Get(serviceName, methodName);
}
} /// <summary>
/// 获取缓存内容
/// </summary>
/// <returns></returns>
public static List<string> GetServiceNames()
{
lock (_locker)
{
return _serviceMap.GetHashIDs();
}
}
/// <summary>
/// 获取服务的全部信息
/// </summary>
/// <param name="serviceName"></param>
/// <returns></returns>
public static Dictionary<string, ServiceInfo> GetAll(string serviceName)
{
lock (_locker)
{
return _serviceMap.GetAll(serviceName);
}
} }
}
测试
至此几个关键点都完成了,下面是vs2017的代码结构:
SAEA.RPCTest是测试项目,Provider为模拟服务端代码、RPCServiceProxy为生成器根据服务端生成的客户端代码,Program.cs中是使用SAEA.RPC使用、测试代码:
using SAEA.Commom;
using SAEA.RPC.Provider;
using SAEA.RPCTest.Consumer;
//using SAEA.RPCTest.Consumer;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks; namespace SAEA.RPCTest
{
class Program
{
static void Main(string[] args)
{
ConsoleHelper.WriteLine($"SAEA.RPC功能测试: {Environment.NewLine} p 启动rpc provider{Environment.NewLine} c 启动rpc consumer{Environment.NewLine} g 启动rpc consumer代码生成器"); var inputStr = ConsoleHelper.ReadLine(); if (string.IsNullOrEmpty(inputStr))
{
inputStr = "p";
} if (inputStr == "c")
{
ConsoleHelper.WriteLine("开始Consumer测试!");
ConsumerInit();
ConsoleHelper.WriteLine("回车结束!");
ConsoleHelper.ReadLine();
}
else if (inputStr == "a")
{
ProviderInit();
ConsoleHelper.WriteLine("回车开始Consumer测试!");
ConsoleHelper.ReadLine();
ConsumerInit();
ConsoleHelper.WriteLine("回车结束!");
ConsoleHelper.ReadLine();
}
else if (inputStr == "g")
{
ConsoleHelper.WriteLine("正在代码生成中...");
Generate();
ConsoleHelper.WriteLine("代码生成完毕,回车结束!");
ConsoleHelper.ReadLine();
}
else
{
ProviderInit();
ConsoleHelper.WriteLine("回车结束!");
ConsoleHelper.ReadLine();
}
} static void ProviderInit()
{
ConsoleHelper.Title = "SAEA.RPC.Provider";
ConsoleHelper.WriteLine("Provider正在启动HelloService。。。");
var sp = new ServiceProvider(new Type[] { typeof(Provider.HelloService) });
sp.Start();
ConsoleHelper.WriteLine("Provider就绪!");
} static void Generate()
{
RPC.Generater.CodeGnerater.Generate(PathHelper.Current, "SAEA.RPCTest");
} static void ConsumerInit()
{
ConsoleHelper.Title = "SAEA.RPC.Consumer"; var url = "rpc://127.0.0.1:39654"; ConsoleHelper.WriteLine($"Consumer正在连接到{url}..."); RPCServiceProxy cp = new RPCServiceProxy(url); ConsoleHelper.WriteLine("Consumer连接成功"); ConsoleHelper.WriteLine("HelloService/Hello:" + cp.HelloService.Hello());
ConsoleHelper.WriteLine("HelloService/Plus:" + cp.HelloService.Plus(, ));
ConsoleHelper.WriteLine("HelloService/Update/UserName:" + cp.HelloService.Update(new Consumer.Model.UserInfo() { ID = , UserName = "yswenli" }).UserName);
ConsoleHelper.WriteLine("HelloService/GetGroupInfo/Creator.UserName:" + cp.HelloService.GetGroupInfo().Creator.UserName);
ConsoleHelper.WriteLine("HelloService/SendData:" + System.Text.Encoding.UTF8.GetString(cp.HelloService.SendData(System.Text.Encoding.UTF8.GetBytes("Hello Data"))));
ConsoleHelper.WriteLine("回车启动性能测试!"); ConsoleHelper.ReadLine(); #region 性能测试 Stopwatch sw = new Stopwatch(); int count = ; ConsoleHelper.WriteLine($"{count} 次实体传输调用测试中..."); var ui = new Consumer.Model.UserInfo() { ID = , UserName = "yswenli" }; sw.Start(); for (int i = ; i < count; i++)
{
cp.HelloService.Update(ui);
}
ConsoleHelper.WriteLine($"实体传输:{count * 1000 / sw.ElapsedMilliseconds} 次/秒"); sw.Stop(); #endregion }
}
}
在命令行中将SAEA.RPCTest发布输入dotnet pulish -r win7-x64后运行exe如下:
至此一个使用方便、高性能rpc就初步完成了。
转载请标明本文来源:https://www.cnblogs.com/yswenli/p/9097217.html
更多内容欢迎star/fork作者的github:https://github.com/yswenli/SAEA
如果发现本文有什么问题和任何建议,也随时欢迎交流~
自行实现 dotnet core rpc的更多相关文章
- 基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始
0x00 简介 DotBPE.RPC是一款基于dotnet core编写的RPC框架,而它的爸爸DotBPE,目标是实现一个开箱即用的微服务框架,但是它还差点意思,还仅仅在构思和尝试的阶段.但不管怎么 ...
- dotnet core各rpc组件的性能测试
一般rpc通讯组件都具有高性特性,因为大部分rpc都是基于二进制和连接复用的特点,相对于HTTP(2.0以下的版本)来说有着很大的性能优势,非常适合服务间通讯交互.本文针对了dotnet core平台 ...
- dotnet core 使用 MongoDB 进行高性能Nosql数据库操作
好久没有写过Blog, 每天看着开源的Java社区流口水, 心里满不是滋味. 终于等到了今年六月份 dotnet core 的正式发布, 看着dotnet 社区也一步一步走向繁荣, 一片蒸蒸日上的大好 ...
- ubuntu15.10 或者 16.04 或者 ElementryOS 下使用 Dotnet Core
这里我们不讲安装,缺少libicu52自行安装. 安装完成后使用dotnet restore或者build都会失败,一是报编译的dll不适合当前系统,二是编译到ubuntu16.04文件夹下会产生一些 ...
- dotnet core开发体验之开始MVC
开始 在上一篇文章:dotnet core多平台开发体验 ,体验了一把dotnet core 之后,现在想对之前做的例子进行改造,想看看加上mvc框架是一种什么样的体验,于是我就要开始诞生今天的这篇文 ...
- 这可能是最low的发布dotnet core站点到centos7
前言 不得不说:我在chrome上写了好长一段,贴了23张图,然后一个crash..我想说我电脑上的chrome已经crash太多次了 以后一定要搞离线编辑的. 正文 什么是.net core,bal ...
- 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)
背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...
- dotnet core webapi +vue 搭建前后端完全分离web架构
架构 服务端采用 dotnet core webapi 前端采用: Vue + router +elementUI+axios 问题 使用前后端完全分离的架构,首先遇到的问题肯定是跨域访问.前后端可 ...
- dotnet core开源博客系统XBlog介绍
XBlog是dotnet core平台下的个人博客开源系统,它只需要通过Copy的方式即可以部署到Linux和windows系统中:如果你有安全证书那只需要简单配置一下即可提供安全的Https服务.接 ...
随机推荐
- Java编程语言下Selenium 鼠标悬停以及右击操作
// 基于Actions类创建一个对象 Actions action = new Actions(driver); // 鼠标悬停在药渡公司全称字段上 action.moveToElement(Yao ...
- CSS的应用下
样式继承: 就是父类的颜色如果变了,子类下的div(或者其他属性)会继承父类的. 参考代码: <!DOCTYPE html> <html lang="en"> ...
- cannot import name '_imaging' 与No module named PIL解决方法
今天学习廖雪峰的python 第三方模块pillow一章. 直接使用from PIL import Image 会报"No module named PIL",显然这是没有安装pi ...
- mysql Access denied for user \'root\'@\'localhost\'” 本人解决方案:
直接上图 昨天还是好的今天就不行了,密码是没错的,就是本地的连接不上,Linux上的mysql可以连, 网上找各种解决方案,什么权限,什么加一句话,还有这个 如果连这个都进不去那就直接重装吧,其实 ...
- SpringCloud实战-Eureka
熟悉微服务架构或Dubbo框架的都知道,微服务中最核心.最基础的组件就是注册中心了.下面利用Spring Cloud Eureka实现服务注册中心.并注册一个简单的服务提供者. 首先先创建一个spir ...
- python命令行使用的问题
python命令行使用的时候要注意一个陷阱,就是如果某个语句不是在>>>下执行的,而是在...下执行的,那么它可能没有执行成功. 例如如下没有成功,原因是上面有一句注释,导致没有执行 ...
- JDK安装:CentOS和Windows环境
Windows上JDK安装 1:下载jdk. 地址在 http://www.oracle.com/index.html >downloads>se>Ja ...
- C#读取shp文件并获取图形保存到sde要素类中(不使用ESRI的类库,纯c#实现)
说明:首先要将sde要素类发布成对应的要素服务,通过对要素服务的操作,实现数据在sde要素类中的增删 //向服务器发出请求 public string getPostData(string postS ...
- Android自动化框架介绍
随着Android应用得越来越广,越来越多的公司推出了自己移动应用测试平台.例如,百度的MTC.东软易测云.Testin云测试平台…….由于自己所在项目组就是做终端测试工具的,故抽空了解了下几种常见的 ...
- ubuntu16+zabbix3.4+grafana环境搭建记录
最近研究了zabbix,稍后放上环境搭建教程,建议想学习搭建的同学记得参考zabbix官网