前言
小李:“胖子,上头叫你对接我的数据好了没有?”
胖子:“那是你的事,你都不提供数据源,我咋接?”
小李:“你想要什么样的数据源?”
胖子:“我想要一个调用简单点的!”
小李:“我这个数据源是在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
如果发现本文有什么问题和任何建议,也随时欢迎交流~
继续阅读与本文标签相同的文章
自已动手做高性能消息队列
C#高性能二进制序列化
-
React 深入说明JSX语法与Props特性
2026-06-02栏目: 教程
-
【云吞铺子之专家来了】RDS for MySQL 表上 Metadata lock 的产生和处理
2026-06-02栏目: 教程
-
ARMS 发布 V2.4.3.3 发布,API支持RAM子账号调用。
2026-06-02栏目: 教程
-
你真的有处理好异常吗
2026-06-02栏目: 教程
-
前端工程化之路-语法检查
2026-06-02栏目: 教程
