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