• 自行实现 dotnet core rpc


    前言

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

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

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

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

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

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

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

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

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

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

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

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

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

    序列化与反序列化

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

      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(object 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<object> clist = new List<object>();
    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<object> clist = new List<object>();
    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<object> clist = new List<object>();
    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+data
    217         /// </summary>
    218         /// <param name="params"></param>
    219         /// <returns></returns>
    220         public static byte[] Serialize(params object[] @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 object[] Deserialize(Type[] types, byte[] datas)
    242         {
    243             List<object> list = new List<object>();
    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 object 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                             try
    359                             {
    360                                 if (!ps[j].PropertyType.IsGenericType)
    361                                 {
    362                                     ps[j].SetValue(instance, Convert.ChangeType(vas[j], ps[j].PropertyType), null);
    363                                 }
    364                                 else
    365                                 {
    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                                     else
    372                                     {
    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                 else
    387                 {
    388                     throw new RPCPamarsException("ParamsSerializeUtil.Deserialize 未定义的类型:" + type.ToString());
    389                 }
    390 
    391             }
    392             return obj;
    393         }
    394 
    395 
    396         private static object DeserializeList(Type type, byte[] datas)
    397         {
    398             List<object> result = new List<object>();
    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                 else
    428                 {
    429                     result.Add(null);
    430                 }
    431             }
    432             return result;
    433         }
    434 
    435         private static object 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<object>);
    442 
    443             return list.ToArray();
    444         }
    445 
    446         private static object 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 object _locker = new object();
     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 object ReversalMethod(MethodInfo action, object obj, object[] args)
     50         {
     51             object 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 object Reversal(IUserToken userToken, string serviceName, string methodName, object[] 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 object[] { 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 object[] { 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                     else
    142                     {
    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             try
    159             {
    160                 object[] 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 object _locker = new object();
     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<object> 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().BaseType.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().BaseType.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             else
    133                 RegistAll();
    134         }
    135         /// <summary>
    136         /// 全部注册带有ServiceAttribute的服务
    137         /// </summary>
    138         public static void RegistAll()
    139         {
    140             StackTrace ss = new StackTrace(true);
    141             MethodBase mb = ss.GetFrame(2).GetMethod();
    142             var space = mb.DeclaringType.Namespace;
    143             var tt = mb.DeclaringType.Assembly.GetTypes();
    144             Regists(tt);
    145         }
    146 
    147         /// <summary>
    148         /// 判断类是否是RPCService
    149         /// </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.Title = "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.Title = "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             #endregion
    113 
    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
    如果发现本文有什么问题和任何建议,也随时欢迎交流~

  • 相关阅读:
    win8应用商店安装路径
    GridView用存储过程实现分页
    Struts学习笔记之HTML标签库
    一个大学毕业生的反思
    JUnit in Action关于Controller的简明阐述
    Struts学习笔记之BEAN标签库
    Struts中web.xml和strutsconfig.xml配置详解
    Os学习笔记之处理机调度
    log4j.properties配置详解
    JUnit学习笔记之简介
  • 原文地址:https://www.cnblogs.com/yswenli/p/9097217.html
Copyright © 2020-2023  润新知