前言

小李:“胖子,上头叫你对接我的数据好了没有?”

胖子:“那是你的事,你都不提供数据源,我咋接?”

小李:“你想要什么样的数据源?”

胖子:“我想要一个调用简单点的!”

小李:“我这个数据源是在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的更多相关文章

  1. 基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始

    0x00 简介 DotBPE.RPC是一款基于dotnet core编写的RPC框架,而它的爸爸DotBPE,目标是实现一个开箱即用的微服务框架,但是它还差点意思,还仅仅在构思和尝试的阶段.但不管怎么 ...

  2. dotnet core各rpc组件的性能测试

    一般rpc通讯组件都具有高性特性,因为大部分rpc都是基于二进制和连接复用的特点,相对于HTTP(2.0以下的版本)来说有着很大的性能优势,非常适合服务间通讯交互.本文针对了dotnet core平台 ...

  3. dotnet core 使用 MongoDB 进行高性能Nosql数据库操作

    好久没有写过Blog, 每天看着开源的Java社区流口水, 心里满不是滋味. 终于等到了今年六月份 dotnet core 的正式发布, 看着dotnet 社区也一步一步走向繁荣, 一片蒸蒸日上的大好 ...

  4. ubuntu15.10 或者 16.04 或者 ElementryOS 下使用 Dotnet Core

    这里我们不讲安装,缺少libicu52自行安装. 安装完成后使用dotnet restore或者build都会失败,一是报编译的dll不适合当前系统,二是编译到ubuntu16.04文件夹下会产生一些 ...

  5. dotnet core开发体验之开始MVC

    开始 在上一篇文章:dotnet core多平台开发体验 ,体验了一把dotnet core 之后,现在想对之前做的例子进行改造,想看看加上mvc框架是一种什么样的体验,于是我就要开始诞生今天的这篇文 ...

  6. 这可能是最low的发布dotnet core站点到centos7

    前言 不得不说:我在chrome上写了好长一段,贴了23张图,然后一个crash..我想说我电脑上的chrome已经crash太多次了 以后一定要搞离线编辑的. 正文 什么是.net core,bal ...

  7. 手把手教你使用spring cloud+dotnet core搭建微服务架构:服务治理(-)

    背景 公司去年开始使用dotnet core开发项目.公司的总体架构采用的是微服务,那时候由于对微服务的理解并不是太深,加上各种组件的不成熟,只是把项目的各个功能通过业务层面拆分,然后通过nginx代 ...

  8. dotnet core webapi +vue 搭建前后端完全分离web架构

    架构 服务端采用 dotnet core  webapi 前端采用: Vue + router +elementUI+axios 问题 使用前后端完全分离的架构,首先遇到的问题肯定是跨域访问.前后端可 ...

  9. dotnet core开源博客系统XBlog介绍

    XBlog是dotnet core平台下的个人博客开源系统,它只需要通过Copy的方式即可以部署到Linux和windows系统中:如果你有安全证书那只需要简单配置一下即可提供安全的Https服务.接 ...

随机推荐

  1. vue2.0 — 移动端的输入框实时检索更新列表

    我们都是行走在这世界的孤独者 - 暖暖 最近在做vue2.0的项目遇到一个移动端实事检索搜索更新列表的效果,但用户在搜索框输入客户的电话或姓名的时候,客户列表内容会做相应的更新,下面给大家看下图~· ...

  2. 体育Bank2016会议笔记

    补注:会议全称应该是体育Bank2016体育投融资总裁年会 新华社体育部徐仁基 演讲主题:帮郭川找到大海-->帮民众找到自己真正的体育爱好 激发和培养体育市场是重中之重 将体育培养成生活习惯.生 ...

  3. javascript DOM编程艺术(检测与性能优化)

    一.对象检测(是否支持js方法):只有支持了该方法才可调用 if(!getElementById || getElementsByTagName){ return false; } 二.性能考滤 1. ...

  4. Hadoop生态圈初识

    一.简介 Hadoop是一个由Apache基金会所开发的分布式系统基础架构.Hadoop的框架最核心的设计就是:HDFS和MapReduce.HDFS为海量的数据提供了存储,则MapReduce为海量 ...

  5. Java多线程:生命周期,实现与调度

    Java线程生命周期 Java线程实现方法 继承Thread类,重写run()方法 实现Runnable接口,便于继承其他类 Callable类替换Runnable类,实现返回值 Future接口对任 ...

  6. QT窗体的小技巧

    1.界面透明 setWindowOpacity(0.8);//构造函数中加此句,1为不透明,0为完全透明,0.8为80%不透明. 2.设置背景图片 QPixmap pixmap = QPixmap(& ...

  7. vue目录结构

    构建新的项目后生成目录结构如下图: 1.build目录下: 最终发布de代码存放的位置 2.config 配置目录,包括端口号等.我们初学可以使用默认的 3.node_modules npm加载的项目 ...

  8. Django入门一之安装及项目创建

    1. 习惯性的创建虚拟环境 # 由于我安装也安装了pyhton3所以在前面要加python2 -m F:\Python Script\MyVirtualenv>python2 -m virtua ...

  9. 虚拟机配置Openstack常见问题汇总

    之前配置了openstack,遇到一些问题,现在将问题全部汇总记录在这里. (1)问题:主机名字修改不了: 原因:没有进入root状态:或者没有正确打开文件,要打开的是/etc/hostname,结果 ...

  10. DX11 Without DirectX SDK--使用Windows SDK来进行开发

    在看龙书(Introduction to 3D Game Programming with Directx 11)的时候,里面所使用的开发工具包为Microsoft DirectX SDK(June ...