前言

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

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

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

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

小李:“我这个数据源是在linux平台使用docker封装发布的,webapi的怎么样?”

胖子:“也行,这项目工期快用完了,你得提供api封装sdk,另外我这边对性能有要求的!”

小李:“webapi多好,基于json各个平台都能对接,性能还不错的!”

胖子:“我只关心我的业务,不是我的业务代码,多一行我都不想码,不然没按时完成算你的!另外用webapi到时候请求量一大,到时候端口用完了,连接不了这锅也得你背!”

小李:“我@##¥%*#¥@#&##@……”

面对胖子这些说辞,小李心里面虽然一万只草泥马在奔腾,但是项目还是要完成是不?另外胖子说的也不无道理!小李作为一个在C#下侵淫多年老鸟,很快想出一个办法——rpc!首先当然是选wcf,这个巨硬的企业级产品在快速开发上除了配置上坑爹了一点,针对客户端的对接真的非常快。小李仔细一研究wcf service 发现目前在linux下玩不了,心里面又是了一阵@##¥%*#¥@#&##@……

胖子:“小李纠结啥,要不就弄个三方的搞一下算了,就算出事了,你说不定都已经离职了,怕啥……”

看着胖子一脸猥琐的表情,小李那是一个气啊,就怪自已平时牛逼吹上天,这时候怎么好怂呢,一咬牙:“你放心,误不了你的事!”。小李一边回复,心里面开始盘算着自行实现一个功能简易,性能高效,使用简单的rpc了。

  上面小李与胖子的场景,在开发的时候也是经典案例,回到正题来:本人认为rpc主要是:调用方法及参数序列化、socket传输、调用方法及参数反序列化、映射到本地并采用与请求相同流程回应客户端的一套方案。其中关键点简单分析主要有:序列化与反序列化、高性能tcp、远程方法反转、客户端代码生成四个方面;tcp还是使用iocp好了,其他接着一一分析。

序列化与反序列化

  序列化与反序列化这个选二进制一般比json的好,ms版的BinaryFormatter 通用性强,但是他的性能、model的标记写法等估计又要被喷了;找到 序列化,结果还是走的类似于soap 这一套,想想算了:本地方法调用都是纳秒级的,io都是毫秒级别的,socket的一次就传这么传这么大一堆,就算局域网也伤不起呀,想轻量化提升性能都难,自行实现一个简单的好了。

  1 /****************************************************************************  2 *Copyright (c) 2018 Microsoft All Rights Reserved.  3 *CLR版本: 4.0.30319.42000  4 *机器名称:WENLI-PC  5 *公司名称:Microsoft  6 *命名空间:SAEA.RPC.Serialize  7 *文件名: SerializeUtil  8 *版本号: V1.0.0.0  9 *唯一标识:9e919430-465d-49a3-91be-b36ac682e283 10 *当前的用户域:WENLI-PC 11 *创建人: yswenli 12 *电子邮箱:wenguoli_520@qq.com 13 *创建时间:2018/5/22 13:17:36 14 *描述: 15 * 16 *===================================================================== 17 *修改标记 18 *修改时间:2018/5/22 13:17:36 19 *修改人: yswenli 20 *版本号: V1.0.0.0 21 *描述: 22 * 23 *****************************************************************************/ 24 using SAEA.RPC.Model; 25 using System; 26 using System.Collections.Generic; 27 using System.Text; 28  29 namespace SAEA.RPC.Serialize 30 { 31     /// <summary> 32     /// rpc参数序列化处理 33     /// </summary> 34     public class ParamsSerializeUtil 35     { 36         /// <summary> 37         /// len+data 38         /// </summary> 39         /// <param name="param"></param> 40         /// <returns></returns> 41         public static byte[] Serialize(  param) 42         { 43             List<byte> datas = new List<byte>(); 44  45             var len = 0; 46             byte[] data = null; 47  48             if (param == null) 49             { 50                 len = 0; 51             } 52             else 53             { 54                 if (param is string) 55                 { 56                     data = Encoding.UTF8.GetBytes((string)param); 57                 } 58                 else if (param is byte) 59                 { 60                     data = new byte[] { (byte)param }; 61                 } 62                 else if (param is bool) 63                 { 64                     data = BitConverter.GetBytes((bool)param); 65                 } 66                 else if (param is short) 67                 { 68                     data = BitConverter.GetBytes((short)param); 69                 } 70                 else if (param is int) 71                 { 72                     data = BitConverter.GetBytes((int)param); 73                 } 74                 else if (param is long) 75                 { 76                     data = BitConverter.GetBytes((long)param); 77                 } 78                 else if (param is float) 79                 { 80                     data = BitConverter.GetBytes((float)param); 81                 } 82                 else if (param is double) 83                 { 84                     data = BitConverter.GetBytes((double)param); 85                 } 86                 else if (param is DateTime) 87                 { 88                     var str = "wl" + ((DateTime)param).Ticks; 89                     data = Encoding.UTF8.GetBytes(str); 90                 } 91                 else if (param is byte[]) 92                 { 93                     data = (byte[])param; 94                 } 95                 else 96                 { 97                     var type = param.GetType(); 98  99                     if (type.IsGenericType || type.IsArray)100                     {101                         data = SerializeList((System.Collections.IEnumerable)param);102                     }103                     else if (type.IsGenericTypeDefinition)104                     {105                         data = SerializeDic((System.Collections.IDictionary)param);106                     }107                     else if (type.IsClass)108                     {109                         var ps = type.GetProperties();110 111                         if (ps != null && ps.Length > 0)112                         {113                             List< > clist = new List< >();114 115                             foreach (var p in ps)116                             {117                                 clist.Add(p.GetValue(param));118                             }119                             data = Serialize(clist.ToArray());120                         }121                     }122                 }123                 len = data.Length;124             }125             datas.AddRange(BitConverter.GetBytes(len));126             if (len > 0)127             {128                 datas.AddRange(data);129             }130             return datas.Count == 0 ? null : datas.ToArray();131         }132 133 134         private static byte[] SerializeList(System.Collections.IEnumerable param)135         {136             List<byte> list = new List<byte>();137 138             if (param != null)139             {140                 List<byte> slist = new List<byte>();141 142                 foreach (var item in param)143                 {144                     var type = item.GetType();145 146                     var ps = type.GetProperties();147                     if (ps != null && ps.Length > 0)148                     {149                         List< > clist = new List< >();150                         foreach (var p in ps)151                         {152                             clist.Add(p.GetValue(item));153                         }154 155                         var clen = 0;156 157                         var cdata = Serialize(clist.ToArray());158 159                         if (cdata != null)160                         {161                             clen = cdata.Length;162                         }163 164                         slist.AddRange(BitConverter.GetBytes(clen));165                         slist.AddRange(cdata);166                     }167                 }168 169                 var len = 0;170 171                 if (slist.Count > 0)172                 {173                     len = slist.Count;174                 }175                 list.AddRange(BitConverter.GetBytes(len));176                 list.AddRange(slist.ToArray());177             }178             return list.ToArray();179         }180 181         private static byte[] SerializeDic(System.Collections.IDictionary param)182         {183             List<byte> list = new List<byte>();184 185             if (param != null && param.Count > 0)186             {187                 foreach (KeyValuePair item in param)188                 {189                     var type = item.GetType();190                     var ps = type.GetProperties();191                     if (ps != null && ps.Length > 0)192                     {193                         List< > clist = new List< >();194                         foreach (var p in ps)195                         {196                             clist.Add(p.GetValue(item));197                         }198                         var clen = 0;199 200                         var cdata = Serialize(clist.ToArray());201 202                         if (cdata != null)203                         {204                             clen = cdata.Length;205                         }206 207                         list.AddRange(BitConverter.GetBytes(clen));208                         list.AddRange(cdata);209                     }210                 }211             }212             return list.ToArray();213         }214 215         /// <summary>216         /// len+data217         /// </summary>218         /// <param name="params"></param>219         /// <returns></returns>220         public static byte[] Serialize(params  [] @params)221         {222             List<byte> datas = new List<byte>();223 224             if (@params != null)225             {226                 foreach (var param in @params)227                 {228                     datas.AddRange(Serialize(param));229                 }230             }231 232             return datas.Count == 0 ? null : datas.ToArray();233         }234 235         /// <summary>236         /// 反序列化237         /// </summary>238         /// <param name="types"></param>239         /// <param name="datas"></param>240         /// <returns></returns>241         public static  [] Deserialize(Type[] types, byte[] datas)242         {243             List< > list = new List< >();244 245             var len = 0;246 247             byte[] data = null;248 249             int offset = 0;250 251             for (int i = 0; i < types.Length; i++)252             {253                 list.Add(Deserialize(types[i], datas, ref offset));254             }255 256             return list.ToArray();257         }258 259         /// <summary>260         /// 反序列化261         /// </summary>262         /// <param name="type"></param>263         /// <param name="datas"></param>264         /// <param name="offset"></param>265         /// <returns></returns>266         public static   Deserialize(Type type, byte[] datas, ref int offset)267         {268             dynamic obj = null;269 270             var len = 0;271 272             byte[] data = null;273 274             len = BitConverter.ToInt32(datas, offset);275             offset += 4;276             if (len > 0)277             {278                 data = new byte[len];279                 Buffer.BlockCopy(datas, offset, data, 0, len);280                 offset += len;281 282                 if (type == typeof(string))283                 {284                     obj = Encoding.UTF8.GetString(data);285                 }286                 else if (type == typeof(byte))287                 {288                     obj = (data);289                 }290                 else if (type == typeof(bool))291                 {292                     obj = (BitConverter.ToBoolean(data, 0));293                 }294                 else if (type == typeof(short))295                 {296                     obj = (BitConverter.ToInt16(data, 0));297                 }298                 else if (type == typeof(int))299                 {300                     obj = (BitConverter.ToInt32(data, 0));301                 }302                 else if (type == typeof(long))303                 {304                     obj = (BitConverter.ToInt64(data, 0));305                 }306                 else if (type == typeof(float))307                 {308                     obj = (BitConverter.ToSingle(data, 0));309                 }310                 else if (type == typeof(double))311                 {312                     obj = (BitConverter.ToDouble(data, 0));313                 }314                 else if (type == typeof(decimal))315                 {316                     obj = (BitConverter.ToDouble(data, 0));317                 }318                 else if (type == typeof(DateTime))319                 {320                     var dstr = Encoding.UTF8.GetString(data);321                     var ticks = long.Parse(dstr.Substring(2));322                     obj = (new DateTime(ticks));323                 }324                 else if (type == typeof(byte[]))325                 {326                     obj = (byte[])data;327                 }328                 else if (type.IsGenericType)329                 {330                     obj = DeserializeList(type, data);331                 }332                 else if (type.IsArray)333                 {334                     obj = DeserializeArray(type, data);335                 }336                 else if (type.IsGenericTypeDefinition)337                 {338                     obj = DeserializeDic(type, data);339                 }340                 else if (type.IsClass)341                 {342                     var instance = Activator.CreateInstance(type);343 344                     var ts = new List<Type>();345 346                     var ps = type.GetProperties();347 348                     if (ps != null)349                     {350                         foreach (var p in ps)351                         {352                             ts.Add(p.PropertyType);353                         }354                         var vas = Deserialize(ts.ToArray(), data);355 356                         for (int j = 0; j < ps.Length; j++)357                         {358                             try359                             {360                                 if (!ps[j].PropertyType.IsGenericType)361                                 {362                                     ps[j].SetValue(instance, Convert.ChangeType(vas[j], ps[j].PropertyType), null);363                                 }364                                 else365                                 {366                                     Type genericTypeDefinition = ps[j].PropertyType.GetGenericTypeDefinition();367                                     if (genericTypeDefinition == typeof(Nullable<>))368                                     {369                                         ps[j].SetValue(instance, Convert.ChangeType(vas[j], Nullable.GetUnderlyingType(ps[j].PropertyType)), null);370                                     }371                                     else372                                     {373                                         //List<T>问题374                                         ps[j].SetValue(instance, Convert.ChangeType(vas[j], ps[j].PropertyType), null);375                                     }376                                 }377                             }378                             catch (Exception ex)379                             {380                                 Console.WriteLine("反序列化不支持的类型:" + ex.Message);381                             }382                         }383                     }384                     obj = (instance);385                 }386                 else387                 {388                     throw new RPCPamarsException("ParamsSerializeUtil.Deserialize 未定义的类型:" + type.ToString());389                 }390 391             }392             return obj;393         }394 395 396         private static   DeserializeList(Type type, byte[] datas)397         {398             List< > result = new List< >();399             var stype = type.GenericTypeArguments[0];400 401             var len = 0;402             var offset = 0;403             //容器大小404             len = BitConverter.ToInt32(datas, offset);405             offset += 4;406             byte[] cdata = new byte[len];407             Buffer.BlockCopy(datas, offset, cdata, 0, len);408             offset += len;409 410             //子项内容411             var slen = 0;412             var soffset = 0;413             while (soffset < len)414             {415                 slen = BitConverter.ToInt32(cdata, soffset);416                 var sdata = new byte[slen + 4];417                 Buffer.BlockCopy(cdata, soffset, sdata, 0, slen + 4);418                 soffset += slen + 4;419 420                 if (slen > 0)421                 {422                     int lloffset = 0;423                     var sobj = Deserialize(stype, sdata, ref lloffset);424                     if (sobj != null)425                         result.Add(sobj);426                 }427                 else428                 {429                     result.Add(null);430                 }431             }432             return result;433         }434 435         private static   DeserializeArray(Type type, byte[] datas)436         {437             var obj = DeserializeList(type, datas);438 439             if (obj == null) return null;440 441             var list = (obj as List< >);442 443             return list.ToArray();444         }445 446         private static   DeserializeDic(Type type, byte[] datas)447         {448             dynamic obj = null;449 450 451 452             return obj;453         }454     }455 }

  实现的过程中,一般结构、类都还比较顺利,但是数组、List、Dictionary还是遇到了一些麻烦,暂时先放着,找到办法再说。真要是传这些,目前先用其他序列化成byte[]来做……

远程方法反转

  远程方法反转即是将接收到的数据定位到本地的对象方法上,如果代码生成、参数使用使用泛型反序列化,理论上是可以提升一些性能的;但是一边写服务业务,一边编写定义结构文件、还一边生成服务代码,本地方法都是纳秒级、相对io的速度来讲,如果为了这点性能提升,在使用的时候估计又是一阵@##¥%*#¥@#&##@……,所以还是使用反射、拆箱吧。

  1 /****************************************************************************  2 *Copyright (c) 2018 Microsoft All Rights Reserved.  3 *CLR版本: 4.0.30319.42000  4 *机器名称:WENLI-PC  5 *公司名称:Microsoft  6 *命名空间:SAEA.RPC.Common  7 *文件名: RPCInovker  8 *版本号: V1.0.0.0  9 *唯一标识:289c03b9-3910-4e15-8072-93243507689c 10 *当前的用户域:WENLI-PC 11 *创建人: yswenli 12 *电子邮箱:wenguoli_520@qq.com 13 *创建时间:2018/5/17 14:11:30 14 *描述: 15 * 16 *===================================================================== 17 *修改标记 18 *修改时间:2018/5/17 14:11:30 19 *修改人: yswenli 20 *版本号: V1.0.0.0 21 *描述: 22 * 23 *****************************************************************************/ 24 using SAEA.RPC.Model; 25 using SAEA.RPC.Net; 26 using SAEA.RPC.Serialize; 27 using SAEA.Sockets.Interface; 28 using System; 29 using System.Linq; 30 using System.Reflection; 31  32 namespace SAEA.RPC.Common 33 { 34     /// <summary> 35     /// RPC将远程调用反转到本地服务 36     /// </summary> 37     public class RPCReversal 38     { 39         static   _locker = new  (); 40          41  42         /// <summary> 43         /// 执行方法 44         /// </summary> 45         /// <param name="action"></param> 46         /// <param name="obj"></param> 47         /// <param name="args"></param> 48         /// <returns></returns> 49         private static   ReversalMethod(MethodInfo action,   obj,  [] args) 50         { 51               result = null; 52             try 53             { 54                 var @params = action.GetParameters(); 55  56                 if (@params != null && @params.Length > 0) 57                 { 58                     result = action.Invoke(obj, args); 59                 } 60                 else 61                 { 62                     result = action.Invoke(obj, null); 63                 } 64             } 65             catch (Exception ex) 66             { 67                 throw new RPCPamarsException($"{obj}/{action.Name},出现异常:{ex.Message}", ex); 68             } 69             return result; 70         } 71  72  73         public static   Reversal(IUserToken userToken, string serviceName, string methodName,  [] inputs) 74         { 75             lock (_locker) 76             { 77                 try 78                 { 79                     var serviceInfo = RPCMapping.Get(serviceName, methodName); 80  81                     if (serviceInfo == null) 82                     { 83                         throw new RPCNotFundException($"当前请求找不到:{serviceName}/{methodName}", null); 84                     } 85  86                     var nargs = new  [] { userToken, serviceName, methodName, inputs }; 87  88                     if (serviceInfo.FilterAtrrs != null && serviceInfo.FilterAtrrs.Count > 0) 89                     { 90                         foreach (var arr in serviceInfo.FilterAtrrs) 91                         { 92                             var goOn = (bool)arr.GetType().GetMethod("OnActionExecuting").Invoke(arr, nargs.ToArray()); 93  94                             if (!goOn) 95                             { 96                                 return new RPCNotFundException("当前逻辑已被拦截!", null); 97                             } 98                         } 99                     }100 101                     if (serviceInfo.ActionFilterAtrrs != null && serviceInfo.ActionFilterAtrrs.Count > 0)102                     {103                         foreach (var arr in serviceInfo.ActionFilterAtrrs)104                         {105                             var goOn = (bool)arr.GetType().GetMethod("OnActionExecuting").Invoke(arr, nargs.ToArray());106 107                             if (!goOn)108                             {109                                 return new RPCNotFundException("当前逻辑已被拦截!", null);110                             }111                         }112                     }113 114                     var result = ReversalMethod(serviceInfo.Mothd, serviceInfo.Instance, inputs);115 116                     nargs = new  [] { userToken, serviceName, methodName, inputs, result };117 118                     if (serviceInfo.FilterAtrrs != null && serviceInfo.FilterAtrrs.Count > 0)119                     {120                         foreach (var arr in serviceInfo.FilterAtrrs)121                         {122                             arr.GetType().GetMethod("OnActionExecuted").Invoke(arr, nargs);123                         }124                     }125 126                     if (serviceInfo.ActionFilterAtrrs != null && serviceInfo.ActionFilterAtrrs.Count > 0)127                     {128                         foreach (var arr in serviceInfo.FilterAtrrs)129                         {130                             arr.GetType().GetMethod("OnActionExecuted").Invoke(arr, nargs);131                         }132                     }133                     return result;134                 }135                 catch (Exception ex)136                 {137                     if (ex.Message.Contains("找不到此rpc方法"))138                     {139                         return new RPCNotFundException("找不到此rpc方法", ex);140                     }141                     else142                     {143                         return new RPCNotFundException("找不到此rpc方法", ex);144                     }145                 }146             }147         }148 149         /// <summary>150         /// 反转到具体的方法上151         /// </summary>152         /// <param name="userToken"></param>153         /// <param name="msg"></param>154         /// <returns></returns>155         public static byte[] Reversal(IUserToken userToken, RSocketMsg msg)156         {157             byte[] result = null;158             try159             {160                  [] inputs = null;161 162                 if (msg.Data != null)163                 {164                     var ptypes = RPCMapping.Get(msg.ServiceName, msg.MethodName).Pamars.Values.ToArray();165 166                     inputs = ParamsSerializeUtil.Deserialize(ptypes, msg.Data);167                 }168 169                 var r = Reversal(userToken, msg.ServiceName, msg.MethodName, inputs);170 171                 if (r != null)172                 {173                     return ParamsSerializeUtil.Serialize(r);174                 }175             }176             catch (Exception ex)177             {178                 throw new RPCPamarsException("RPCInovker.Invoke error:" + ex.Message, ex);179             }180             return result;181 182         }183     }184 }

客户端代码生成

  为了方便客户使用rpc,所以有rpc相关的代码在客户端那肯定是越少越好,如果光服务端方便,客户端估计又要@##¥%*#¥@#&##@……,所以将一些rpc相关代码生成好,客户端透明调用是必须的。

  1 /****************************************************************************  2 *Copyright (c) 2018 Microsoft All Rights Reserved.  3 *CLR版本: 4.0.30319.42000  4 *机器名称:WENLI-PC  5 *公司名称:Microsoft  6 *命名空间:SAEA.RPC.Generater  7 *文件名: CodeGnerater  8 *版本号: V1.0.0.0  9 *唯一标识:59ba5e2a-2fd0-444b-a260-ab68c726d7ee 10 *当前的用户域:WENLI-PC 11 *创建人: yswenli 12 *电子邮箱:wenguoli_520@qq.com 13 *创建时间:2018/5/17 18:30:57 14 *描述: 15 * 16 *===================================================================== 17 *修改标记 18 *修改时间:2018/5/17 18:30:57 19 *修改人: yswenli 20 *版本号: V1.0.0.0 21 *描述: 22 * 23 *****************************************************************************/ 24 using SAEA.RPC.Common; 25 using SAEA.RPC.Model; 26 using System; 27 using System.Collections.Generic; 28 using System.IO; 29 using System.Linq; 30 using System.Reflection; 31 using System.Text; 32  33 namespace SAEA.RPC.Generater 34 { 35     /// <summary> 36     /// 代码生成器 37     /// </summary> 38     public static class CodeGnerater 39     { 40         static string space4 = "    "; 41  42         /// <summary> 43         /// 获取指定数量的空格 44         /// </summary> 45         /// <param name="num"></param> 46         /// <returns></returns> 47         static string GetSpace(int num = 1) 48         { 49             var sb = new StringBuilder(); 50  51             for (int i = 0; i < num; i++) 52             { 53                 sb.Append(space4); 54             } 55  56             return sb.ToString(); 57         } 58  59         /// <summary> 60         /// 获取变量名 61         /// </summary> 62         /// <param name="str"></param> 63         /// <returns></returns> 64         static string GetSuffixStr(string str) 65         { 66             return "_" + str.Substring(0, 1).ToLower() + str.Substring(1); 67         } 68  69         /// <summary> 70         /// 生成代码头部 71         /// </summary> 72         /// <returns></returns> 73         static string Header(params string[] usings) 74         { 75             var sb = new StringBuilder(); 76             sb.AppendLine("/*******"); 77             sb.AppendLine($"*此代码为SAEA.RPCGenerater生成 {DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}"); 78             sb.AppendLine("*******/" + Environment.NewLine); 79             sb.AppendLine("using System;"); 80             if (usings != null) 81             { 82                 foreach (var u in usings) 83                 { 84                     sb.AppendLine(u); 85                 } 86             } 87             return sb.ToString(); 88         } 89  90         static string _proxyStr; 91  92         static List<string> _serviceStrs = new List<string>(); 93  94         static Dictionary<string, string> _modelStrs = new Dictionary<string, string>(); 95  96         /// <summary> 97         /// 生成代理代码 98         /// </summary> 99         /// <param name="spaceName"></param>100         internal static void GenerateProxy(string spaceName)101         {102             StringBuilder csStr = new StringBuilder();103             csStr.AppendLine(Header("using SAEA.RPC.Consumer;", $"using {spaceName}.Consumer.Model;", $"using {spaceName}.Consumer.Service;"));104             csStr.AppendLine($"namespace {spaceName}.Consumer");105             csStr.AppendLine("{");106             csStr.AppendLine($"{GetSpace(1)}public class RPCServiceProxy");107             csStr.AppendLine(GetSpace(1) + "{");108 109             csStr.AppendLine(GetSpace(2) + "ServiceConsumer _serviceConsumer;");110             csStr.AppendLine(GetSpace(2) + "public RPCServiceProxy(string uri = "rpc://127.0.0.1:39654") : this(new Uri(uri)){}");111             csStr.AppendLine(GetSpace(2) + "public RPCServiceProxy(Uri uri)");112             csStr.AppendLine(GetSpace(2) + "{");113 114             csStr.AppendLine(GetSpace(3) + "_serviceConsumer = new ServiceConsumer(uri);");115 116             var names = RPCMapping.GetServiceNames();117 118             if (names != null)119             {120                 foreach (var name in names)121                 {122                     csStr.AppendLine(GetSpace(3) + GetSuffixStr(name) + $" = new {name}(_serviceConsumer);");123                 }124             }125             csStr.AppendLine(GetSpace(2) + "}");126 127             if (names != null)128             {129                 foreach (var name in names)130                 {131                     var suffixStr = GetSuffixStr(name);132 133                     csStr.AppendLine(GetSpace(2) + $"{name} {suffixStr};");134                     csStr.AppendLine(GetSpace(2) + $"public {name} {name}");135                     csStr.AppendLine(GetSpace(2) + "{");136                     csStr.AppendLine($"{GetSpace(3)} get{{ return {suffixStr}; }}");137                     csStr.AppendLine(GetSpace(2) + "}");138 139                     var list = RPCMapping.GetAll(name);140                     if (list != null)141                     {142                         GenerateService(spaceName, name, list);143                     }144                 }145             }146 147             csStr.AppendLine(GetSpace(1) + "}");148             csStr.AppendLine("}");149             _proxyStr = csStr.ToString();150         }151         /// <summary>152         /// 生成调用服务代码153         /// </summary>154         /// <param name="spaceName"></param>155         /// <param name="serviceName"></param>156         /// <param name="methods"></param>157         internal static void GenerateService(string spaceName, string serviceName, Dictionary<string, ServiceInfo> methods)158         {159             StringBuilder csStr = new StringBuilder();160             csStr.AppendLine($"namespace {spaceName}.Consumer.Service");161             csStr.AppendLine("{");162             csStr.AppendLine($"{GetSpace(1)}public class {serviceName}");163             csStr.AppendLine(GetSpace(1) + "{");164             csStr.AppendLine(GetSpace(2) + "ServiceConsumer _serviceConsumer;");165             csStr.AppendLine(GetSpace(2) + $"public {serviceName}(ServiceConsumer serviceConsumer)");166             csStr.AppendLine(GetSpace(2) + "{");167             csStr.AppendLine(GetSpace(3) + "_serviceConsumer = serviceConsumer;");168             csStr.AppendLine(GetSpace(2) + "}");169 170             foreach (var item in methods)171             {172                 var rtype = item.Value.Mothd.ReturnType;173 174                 if (rtype != null)175                 {176                     if (!_modelStrs.ContainsKey($"{spaceName}.Consumer.Model.{rtype.Name}"))177                     {178                         GenerateModel(spaceName, rtype);179                     }180                 }181 182                 var argsStr = new StringBuilder();183 184                 var argsInput = new StringBuilder();185 186                 if (item.Value.Pamars != null)187                 {188                     int i = 0;189                     foreach (var arg in item.Value.Pamars)190                     {191                         i++;192                         argsStr.Append(arg.Value.Name);193                         argsStr.Append(" ");194                         argsStr.Append(arg.Key);195                         if (i < item.Value.Pamars.Count)196                             argsStr.Append(", ");197 198                         if (arg.Value != null && arg.Value.IsClass)199                         {200                             if (!_modelStrs.ContainsKey($"{spaceName}.Consumer.Model.{arg.Value.Name}"))201                             {202                                 GenerateModel(spaceName, arg.Value);203                             }204                         }205 206                         argsInput.Append(", ");207                         argsInput.Append(arg.Key);208                     }209                 }210 211                 csStr.AppendLine(GetSpace(2) + $"public {rtype.Name} {item.Key}({argsStr.ToString()})");212                 csStr.AppendLine(GetSpace(2) + "{");213                 csStr.AppendLine(GetSpace(3) + $"return _serviceConsumer.RemoteCall<{rtype.Name}>("{serviceName}", "{item.Key}"{argsInput.ToString()});");214                 csStr.AppendLine(GetSpace(2) + "}");215 216 217             }218 219             csStr.AppendLine(GetSpace(1) + "}");220             csStr.AppendLine("}");221             _serviceStrs.Add(csStr.ToString());222         }223 224         /// <summary>225         /// 生成实体代码226         /// </summary>227         /// <typeparam name="T"></typeparam>228         /// <param name="t"></param>229         /// <returns></returns>230         internal static void GenerateModel(string spaceName, Type type)231         {232             if (!IsModel(type)) return;233             StringBuilder csStr = new StringBuilder();234             csStr.AppendLine($"namespace {spaceName}.Consumer.Model");235             csStr.AppendLine("{");236             csStr.AppendLine($"{GetSpace(1)}public class {type.Name}");237             csStr.AppendLine(GetSpace(1) + "{");238             var ps = type.GetProperties();239             foreach (var p in ps)240             {241                 csStr.AppendLine($"{GetSpace(2)}public {p.PropertyType.Name} {p.Name}");242                 csStr.AppendLine(GetSpace(2) + "{");243                 csStr.AppendLine(GetSpace(3) + "get;set;");244                 csStr.AppendLine(GetSpace(2) + "}");245             }246             csStr.AppendLine(GetSpace(1) + "}");247             csStr.AppendLine("}");248             _modelStrs.Add($"{spaceName}.Consumer.Model.{type.Name}", csStr.ToString());249         }250 251         /// <summary>252         /// 是否是实体253         /// </summary>254         /// <param name="type"></param>255         /// <returns></returns>256         internal static bool IsModel(Type type)257         {258             if (type.IsArray || type.IsSealed || !type.IsClass)259             {260                 return false;261             }262             return true;263         }264 265         /// <summary>266         /// 生成客户端C#代码文件267         /// </summary>268         /// <param name="folder"></param>269         /// <param name="spaceName"></param>270         public static void Generate(string folder, string spaceName)271         {272             RPCMapping.RegistAll();273 274             GenerateProxy(spaceName);275 276             var filePath = Path.Combine(folder, "RPCServiceProxy.cs");277 278             StringBuilder sb = new StringBuilder();279 280             sb.AppendLine(_proxyStr);281 282             if (_serviceStrs != null && _serviceStrs.Count > 0)283             {284                 foreach (var serviceStr in _serviceStrs)285                 {286                     sb.AppendLine(serviceStr);287                 }288             }289 290             if (_modelStrs != null && _modelStrs.Count > 0)291             {292                 foreach (var entry in _modelStrs)293                 {294                     sb.AppendLine(entry.Value);295                 }296             }297 298             if (File.Exists(filePath))299                 File.Delete(filePath);300 301             File.WriteAllText(filePath, sb.ToString(), Encoding.UTF8);302         }303 304 305     }306 }

  无论在服务端根据数据将远程调用反转本地方法、还是生成客户端代码的过程都离不开服务结构的问题。如果是根据结构文件来处理,则先要编写结构文件;服务端码农活不重事不多啊?文档没发你啊?啥锅都往这边甩……此处省略一万字。另外一种方式就是类似web mvc采用约定方式,写完服务业务代码后,再自动生成结构并缓存在内存里。

  1 /****************************************************************************  2 *Copyright (c) 2018 Microsoft All Rights Reserved.  3 *CLR版本: 4.0.30319.42000  4 *机器名称:WENLI-PC  5 *公司名称:Microsoft  6 *命名空间:SAEA.RPC.Provider  7 *文件名: ServiceTable  8 *版本号: V1.0.0.0  9 *唯一标识:e95f1d0b-f172-49c7-b75f-67f333504260 10 *当前的用户域:WENLI-PC 11 *创建人: yswenli 12 *电子邮箱:wenguoli_520@qq.com 13 *创建时间:2018/5/16 17:46:34 14 *描述: 15 * 16 *===================================================================== 17 *修改标记 18 *修改时间:2018/5/16 17:46:34 19 *修改人: yswenli 20 *版本号: V1.0.0.0 21 *描述: 22 * 23 *****************************************************************************/ 24 using SAEA.Commom; 25 using SAEA.RPC.Model; 26 using System; 27 using System.Collections.Concurrent; 28 using System.Collections.Generic; 29 using System.Diagnostics; 30 using System.Linq; 31 using System.Reflection; 32  33 namespace SAEA.RPC.Common 34 { 35     /// <summary> 36     /// 服务类缓存表 37     /// md5+ServiceInfo反射结果 38     /// </summary> 39     internal static class RPCMapping 40     { 41         static   _locker = new  (); 42  43         static HashMap<string, string, ServiceInfo> _serviceMap = new HashMap<string, string, ServiceInfo>(); 44  45         /// <summary> 46         /// 本地注册RPC服务缓存 47         /// </summary> 48         public static HashMap<string, string, ServiceInfo> ServiceMap 49         { 50             get 51             { 52                 return _serviceMap; 53             } 54         } 55  56         /// <summary> 57         /// 本地注册RPC服务 58         /// </summary> 59         /// <param name="type"></param> 60         public static void Regist(Type type) 61         { 62             lock (_locker) 63             { 64                 var serviceName = type.Name; 65  66                 if (IsRPCService(type)) 67                 { 68                     var methods = type.GetMethods(); 69  70                     var rms = GetRPCMehod(methods); 71  72                     if (rms.Count > 0) 73                     { 74                         foreach (var m in rms) 75                         { 76                             var serviceInfo = new ServiceInfo() 77                             { 78                                 Type = type, 79                                 Instance = Activator.CreateInstance(type), 80                                 Mothd = m, 81                                 Pamars = m.GetParameters().ToDic() 82                             }; 83  84                             List< > iAttrs = null; 85  86                             //类上面的过滤 87                             var attrs = type.GetCustomAttributes(true); 88  89                             if (attrs != null && attrs.Length > 0) 90                             { 91                                 var classAttrs = attrs.Where(b => b.GetType(). Type.Name == "ActionFilterAttribute").ToList(); 92  93                                 if (classAttrs != null && classAttrs.Count > 0) 94  95                                     iAttrs = classAttrs; 96  97                             } 98  99                             serviceInfo.FilterAtrrs = iAttrs;100 101                             //action上面的过滤102                             var actionAttrs = m.GetCustomAttributes(true);103 104                             if (actionAttrs != null)105                             {106                                 var filterAttrs = attrs.Where(b => b.GetType(). Type.Name == "ActionFilterAttribute").ToList();107 108                                 if (filterAttrs != null && filterAttrs.Count > 0)109 110                                     serviceInfo.ActionFilterAtrrs = filterAttrs;111                             }112 113                             _serviceMap.Set(serviceName, m.Name, serviceInfo);114                         }115                     }116                 }117             }118         }119 120         /// <summary>121         /// 本地注册RPC服务122         /// 若为空,则默认全部注册带有ServiceAttribute的服务123         /// </summary>124         /// <param name="types"></param>125         public static void Regists(params Type[] types)126         {127             if (types != null)128                 foreach (var type in types)129                 {130                     Regist(type);131                 }132             else133                 RegistAll();134         }135         /// <summary>136         /// 全部注册带有ServiceAttribute的服务137         /// </summary>138         public static void RegistAll()139         {140             StackTrace ss = new StackTrace(true);141             Method  mb = ss.Get (2).GetMethod();142             var space = mb.DeclaringType.Namespace;143             var tt = mb.DeclaringType.Assembly.GetTypes();144             Regists(tt);145         }146 147         /// <summary>148         /// 判断类是否是RPCService149         /// </summary>150         /// <param name="type"></param>151         /// <returns></returns>152         public static bool IsRPCService(Type type)153         {154             var isService = false;155             var cAttrs = type.GetCustomAttributes(true);156             if (cAttrs != null)157             {158                 foreach (var cAttr in cAttrs)159                 {160                     if (cAttr is RPCServiceAttribute)161                     {162                         isService = true;163                         break;164                     }165                 }166             }167             return isService;168         }169 170         /// <summary>171         /// 获取RPC方法集合172         /// </summary>173         /// <param name="mInfos"></param>174         /// <returns></returns>175         public static List<MethodInfo> GetRPCMehod(MethodInfo[] mInfos)176         {177             List<MethodInfo> result = new List<MethodInfo>();178             if (mInfos != null)179             {180                 var isRPC = false;181                 foreach (var method in mInfos)182                 {183                     if (method.IsAbstract || method.IsConstructor || method.IsFamily || method.IsPrivate || method.IsStatic || method.IsVirtual)184                     {185                         break;186                     }187                     188                     isRPC = true;189                     var attrs = method.GetCustomAttributes(true);190                     if (attrs != null)191                     {192                         foreach (var attr in attrs)193                         {194                             if (attr is NoRpcAttribute)195                             {196                                 isRPC = false;197                                 break;198                             }199                         }200                     }201                     if (isRPC)202                     {203                         result.Add(method);204                     }205                 }206             }207             return result;208         }209 210         /// <summary>211         /// 转换成字典212         /// </summary>213         /// <param name="parameterInfos"></param>214         /// <returns></returns>215         public static Dictionary<string, Type> ToDic(this ParameterInfo[] parameterInfos)216         {217             if (parameterInfos == null) return null;218 219             Dictionary<string, Type> dic = new Dictionary<string, Type>();220 221             foreach (var p in parameterInfos)222             {223                 dic.Add(p.Name, p.ParameterType);224             }225 226             return dic;227         }228 229 230         /// <summary>231         /// 获取缓存内容232         /// </summary>233         /// <param name="serviceName"></param>234         /// <param name="methodName"></param>235         /// <returns></returns>236         public static ServiceInfo Get(string serviceName, string methodName)237         {238             lock (_locker)239             {240                 return _serviceMap.Get(serviceName, methodName);241             }242         }243 244         /// <summary>245         /// 获取缓存内容246         /// </summary>247         /// <returns></returns>248         public static List<string> GetServiceNames()249         {250             lock (_locker)251             {252                 return _serviceMap.GetHashIDs();253             }254         }255         /// <summary>256         /// 获取服务的全部信息257         /// </summary>258         /// <param name="serviceName"></param>259         /// <returns></returns>260         public static Dictionary<string, ServiceInfo> GetAll(string serviceName)261         {262             lock (_locker)263             {264                 return _serviceMap.GetAll(serviceName);265             }266         }267 268 269 270     }271 }

测试

  至此几个关键点都完成了,下面是vs2017的代码结构:

  SAEA.RPCTest是测试项目,Provider为模拟服务端代码、RPCServiceProxy为生成器根据服务端生成的客户端代码,Program.cs中是使用SAEA.RPC使用、测试代码:

  1 using SAEA.Commom;  2 using SAEA.RPC.Provider;  3 using SAEA.RPCTest.Consumer;  4 //using SAEA.RPCTest.Consumer;  5 using System;  6 using System.Diagnostics;  7 using System.Threading;  8 using System.Threading.Tasks;  9  10 namespace SAEA.RPCTest 11 { 12     class Program 13     { 14         static void Main(string[] args) 15         { 16             ConsoleHelper.WriteLine($"SAEA.RPC功能测试: {Environment.NewLine}   p 启动rpc provider{Environment.NewLine}   c 启动rpc consumer{Environment.NewLine}   g 启动rpc consumer代码生成器"); 17  18             var inputStr = ConsoleHelper.ReadLine(); 19  20             if (string.IsNullOrEmpty(inputStr)) 21             { 22                 inputStr = "p"; 23             } 24  25             if (inputStr == "c") 26             { 27                 ConsoleHelper.WriteLine("开始Consumer测试!"); 28                 ConsumerInit(); 29                 ConsoleHelper.WriteLine("回车结束!"); 30                 ConsoleHelper.ReadLine(); 31             } 32             else if (inputStr == "a") 33             { 34                 ProviderInit(); 35                 ConsoleHelper.WriteLine("回车开始Consumer测试!"); 36                 ConsoleHelper.ReadLine(); 37                 ConsumerInit(); 38                 ConsoleHelper.WriteLine("回车结束!"); 39                 ConsoleHelper.ReadLine(); 40             } 41             else if (inputStr == "g") 42             { 43                 ConsoleHelper.WriteLine("正在代码生成中..."); 44                 Generate(); 45                 ConsoleHelper.WriteLine("代码生成完毕,回车结束!"); 46                 ConsoleHelper.ReadLine(); 47             } 48             else 49             { 50                 ProviderInit(); 51                 ConsoleHelper.WriteLine("回车结束!"); 52                 ConsoleHelper.ReadLine(); 53             } 54         } 55  56  57         static void ProviderInit() 58         { 59             ConsoleHelper.  = "SAEA.RPC.Provider"; 60             ConsoleHelper.WriteLine("Provider正在启动HelloService。。。"); 61             var sp = new ServiceProvider(new Type[] { typeof(Provider.HelloService) }); 62             sp.Start(); 63             ConsoleHelper.WriteLine("Provider就绪!"); 64         } 65  66         static void Generate() 67         { 68             RPC.Generater.CodeGnerater.Generate(PathHelper.Current, "SAEA.RPCTest"); 69         } 70  71         static void ConsumerInit() 72         { 73             ConsoleHelper.  = "SAEA.RPC.Consumer"; 74  75             var url = "rpc://127.0.0.1:39654"; 76  77             ConsoleHelper.WriteLine($"Consumer正在连接到{url}..."); 78  79             RPCServiceProxy cp = new RPCServiceProxy(url); 80  81             ConsoleHelper.WriteLine("Consumer连接成功"); 82  83             ConsoleHelper.WriteLine("HelloService/Hello:" + cp.HelloService.Hello()); 84             ConsoleHelper.WriteLine("HelloService/Plus:" + cp.HelloService.Plus(1, 9)); 85             ConsoleHelper.WriteLine("HelloService/Update/UserName:" + cp.HelloService.Update(new Consumer.Model.UserInfo() { ID = 1, UserName = "yswenli" }).UserName); 86             ConsoleHelper.WriteLine("HelloService/GetGroupInfo/Creator.UserName:" + cp.HelloService.GetGroupInfo(1).Creator.UserName); 87             ConsoleHelper.WriteLine("HelloService/SendData:" + System.Text.Encoding.UTF8.GetString(cp.HelloService.SendData(System.Text.Encoding.UTF8.GetBytes("Hello Data")))); 88             ConsoleHelper.WriteLine("回车启动性能测试!"); 89  90             ConsoleHelper.ReadLine(); 91  92             #region 性能测试 93  94             Stopwatch sw = new Stopwatch(); 95  96             int count = 1000000; 97  98             ConsoleHelper.WriteLine($"{count} 次实体传输调用测试中..."); 99 100             var ui = new Consumer.Model.UserInfo() { ID = 1, UserName = "yswenli" };101 102             sw.Start();103 104             for (int i = 0; i < count; i++)105             {106                 cp.HelloService.Update(ui);107             }108             ConsoleHelper.WriteLine($"实体传输:{count * 1000 / sw.ElapsedMilliseconds} 次/秒");109 110             sw.Stop();111 112             #endregion113 114 115 116         }117     }118 }

  在命令行中将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
如果发现本文有什么问题和任何建议,也随时欢迎交流~

收藏 打印