前言

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

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

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

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

小李:“我这个数据源是在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. Java编程语言下Selenium 鼠标悬停以及右击操作

    // 基于Actions类创建一个对象 Actions action = new Actions(driver); // 鼠标悬停在药渡公司全称字段上 action.moveToElement(Yao ...

  2. CSS的应用下

    样式继承: 就是父类的颜色如果变了,子类下的div(或者其他属性)会继承父类的. 参考代码: <!DOCTYPE html> <html lang="en"> ...

  3. cannot import name '_imaging' 与No module named PIL解决方法

    今天学习廖雪峰的python 第三方模块pillow一章. 直接使用from PIL import Image 会报"No module named PIL",显然这是没有安装pi ...

  4. mysql Access denied for user \'root\'@\'localhost\'” 本人解决方案:

    直接上图   昨天还是好的今天就不行了,密码是没错的,就是本地的连接不上,Linux上的mysql可以连, 网上找各种解决方案,什么权限,什么加一句话,还有这个 如果连这个都进不去那就直接重装吧,其实 ...

  5. SpringCloud实战-Eureka

    熟悉微服务架构或Dubbo框架的都知道,微服务中最核心.最基础的组件就是注册中心了.下面利用Spring Cloud Eureka实现服务注册中心.并注册一个简单的服务提供者. 首先先创建一个spir ...

  6. python命令行使用的问题

    python命令行使用的时候要注意一个陷阱,就是如果某个语句不是在>>>下执行的,而是在...下执行的,那么它可能没有执行成功. 例如如下没有成功,原因是上面有一句注释,导致没有执行 ...

  7. JDK安装:CentOS和Windows环境

    Windows上JDK安装             1:下载jdk.  地址在  http://www.oracle.com/index.html  >downloads>se>Ja ...

  8. C#读取shp文件并获取图形保存到sde要素类中(不使用ESRI的类库,纯c#实现)

    说明:首先要将sde要素类发布成对应的要素服务,通过对要素服务的操作,实现数据在sde要素类中的增删 //向服务器发出请求 public string getPostData(string postS ...

  9. Android自动化框架介绍

    随着Android应用得越来越广,越来越多的公司推出了自己移动应用测试平台.例如,百度的MTC.东软易测云.Testin云测试平台…….由于自己所在项目组就是做终端测试工具的,故抽空了解了下几种常见的 ...

  10. ubuntu16+zabbix3.4+grafana环境搭建记录

    最近研究了zabbix,稍后放上环境搭建教程,建议想学习搭建的同学记得参考zabbix官网