一, 关于 RPC 的调用
1. 调用者 (客户端 Client) 以本地调用的方式发起调用;
2. Client stub(客户端存根)收到调用后, 负责将被调用的方法名, 参数等打包编码成特定格式的能进行网络传输的消息体;
3. Client stub 将消息体通过网络发送给服务端;
4. Server stub(服务端存根)收到通过网络接收到消息后按照相应格式进行拆包解码, 获取方法名和参数;
5. Server stub 根据方法名和参数进行本地调用;
6. 被调用者 (Server) 本地调用执行后将结果返回给 server stub;
7. Server stub 将返回值打包编码成消息, 并通过网络发送给客户端;
8. Client stub 收到消息后, 进行拆包解码, 返回给 Client;
9. Client 得到本次 RPC 调用的最终结果.
参考 https://www.cnblogs.com/FG123/p/10261676.html
参考 https://www.jianshu.com/p/bb9beca7f7bc 第四节
二, 关于 RPC 调用方式的思考(为什么要用代理类)
RPC 的方便之处我们已经看到了,
假设在系统中要调用多个服务, 如果写一个函数, 每次将这个服务的名字, 参数, 和其他信息通过一个方法来调用远程服务, 假设这个方法叫做 getService(methodname,object[], 参数 3, 参数 4)
我们在各个消费类中来调用这个方法似乎也能得到结果.
在每个调用远程服务的地方都要反射出 类的方法名称, 参数等其他信息以能传给 getService 是不是很麻烦?
要知道远程服务每个服务返回的结果不会是一样的类型, 那我们在客户端还要每次都转换 getService 的结果, 是不是很麻烦?
有没有好的解决方案?
-- 请使用代理类, 我们在代理类中反射代理接口得到这个方法的各种属性 (名称 & 参数 & 其他), 远程调用传递给远程服务, 并转换得到的结果. 看起来这种方法和上文的 getService 差不多嘛! 那我们为什么要使用代理类呢?(我也不知道, 但看起来很吊的样子.) 这看起来并没有很好的样子, 况且如果有多个类要调用远程服务, 那岂不是要写很多代理类?
思考: 调用 getService 后每次都要在消费类中转换结果, 使用代理类后将这个转换过程放入了代理类中, 这样消费类就不用关注 RPC 的调用结果的类型的转换了.
于是人们发明了动态代理 -- 来自《鲁迅先生说革命》
人们发现每个类都要写个代理. 现在小明要在项目中写 1000 个代理类, 直接气炸了, 对! 炸了!.
经过了 N 代的小明客户钻研和发现, 总结了一套可以很省力气的方法.-- 动态代理
简单的来说: 动态创建代理类(https://www.cnblogs.com/netqq/p/11452374.html), 这样就不用给每个消费类都写一个代理类了, 是不很爽
三, 动态代理与 RPC
在网上找到了一个简单的 RPC 示例, 非常适合初学者学习 https://github.com/Coldairarrow/DotNettyRPC
下载项目后先运行 Server 项目, 再运行 client 项目
看到再 server 的控制台上输出了 hello 字符串. 这是客户端程序调用了 server 的 IHello.SayHello()的服务输出的.
我们来看下作者的客户端调用
RPCClientFactory 源码如下
- namespace Coldairarrow.DotNettyRPC
- {
- /// <summary>
- /// 客户端工厂
- /// </summary>
- public class RPCClientFactory
- {
- private static ConcurrentDictionary<string, object> _services { get; } = new ConcurrentDictionary<string, object>();
- /// <summary>
- /// 获取客户端
- /// 注: 默认服务名为接口名
- /// </summary>
- /// <typeparam name="T">接口定义类型</typeparam>
- /// <param name="serverIp">远程服务 IP</param>
- /// <param name="port">远程服务端口</param>
- /// <returns></returns>
- public static T GetClient<T>(string serverIp, int port) where T : class
- {
- return GetClient<T>(serverIp, port, typeof(T).Name);
- }
- /// <summary>
- /// 获取客户端
- /// 注: 自定义服务名
- /// </summary>
- /// <typeparam name="T">接口定义类型</typeparam>
- /// <param name="serverIp">远程服务 IP</param>
- /// <param name="port">远程服务端口</param>
- /// <param name="serviceName">服务名</param>
- /// <returns></returns>
- public static T GetClient<T>(string serverIp, int port, string serviceName) where T : class
- {
- T service = null;
- string key = $"{serviceName}-{serverIp}-{port}";
- try
- {
- service = (T)_services[key];
- }
- catch
- {
- var clientProxy = new RPCClientProxy
- {
- ServerIp = serverIp,
- ServerPort = port,
- ServiceType = typeof(T),
- ServiceName = serviceName
- };
- service = clientProxy.ActLike<T>();
- // 动态代理?
- _services[key] = service;
- }
- return service;
- }
- }
- }
- View Code
在示例中, 程序调用的 GetClient
实际上也是动态生成的代理类, 返回了 IHello 类型的对象.
我们先抛开该作者的程序, 用我们自己的动态代理类来实现相同的效果.
在 DotNettyRPC 项目中添加 ProxyDecorator<T> 类. 需要 nuget 下载 System.Reflection.DispatchProxy.dll
在添加 ProxyDecorator 和编译的时候会遇到问题, 我们将 server, client 项目和 DotNettyRPC 转为 NETCORE 项目才能正常执行 , 因为 System.Reflection.DispatchProxy.dll 只 NETCORE 类库, 不支持 NET Framework 项目
ProxyDecorator<T> 源码
- public class ProxyDecorator<T> : DispatchProxy
- {
- public string ServerIp { get; set; }
- public int ServerPort { get; set; }
- public string ServiceName { get; set; }
- static Bootstrap _bootstrap { get; }
- static ClientWait _clientWait { get; } = new ClientWait();
- static ProxyDecorator()
- {
- _bootstrap = new Bootstrap()
- .Group(new MultithreadEventLoopGroup())
- .Channel<TcpSocketChannel>()
- .Option(ChannelOption.TcpNodelay, true)
- .Handler(new ActionChannelInitializer<ISocketChannel>(channel =>
- {
- IChannelPipeline pipeline = channel.Pipeline;
- pipeline.AddLast("framing-enc", new LengthFieldPrepender(8));
- pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 8, 0, 8));
- pipeline.AddLast(new ClientHandler(_clientWait));
- }));
- }
- public ProxyDecorator()
- {
- }
- ///// <summary>
- ///// 创建代理实例
- ///// </summary>
- ///// <param name="decorated">代理的接口类型</param>
- ///// <returns></returns>
- public T Create(string serverIp, int port, string serviceName)
- {
- object proxy = Create<T, ProxyDecorator<T>>(); // 调用 DispatchProxy 的 Create 创建一个新的 T
- ((ProxyDecorator<T>)proxy).ServerIp = serverIp;
- ((ProxyDecorator<T>)proxy).ServerPort = port;
- ((ProxyDecorator<T>)proxy).ServiceName = serviceName;
- return (T)proxy;
- }
- protected override object Invoke(MethodInfo targetMethod, object[] args)
- {
- if (targetMethod == null) throw new Exception("无效的方法");
- try
- {
- ResponseModel response = null;
- IChannel client = null;
- try
- {
- client = AsyncHelpers.RunSync(() => _bootstrap.ConnectAsync($"{ServerIp}:{ServerPort}".ToIPEndPoint()));
- }
- catch
- {
- throw new Exception("连接到服务端失败!");
- }
- if (client != null)
- {
- _clientWait.Start(client.Id.AsShortText());
- RequestModel requestModel = new RequestModel
- {
- ServiceName = ServiceName,
- MethodName = targetMethod.Name,
- Paramters = args.ToList()
- };
- var sendBuffer = Unpooled.WrappedBuffer(requestModel.ToJson().ToBytes(Encoding.UTF8));
- client.WriteAndFlushAsync(sendBuffer);
- var responseStr = _clientWait.Wait(client.Id.AsShortText()).ResponseString;
- response = responseStr.ToObject<ResponseModel>();
- }
- else
- {
- throw new Exception("连接到服务端失败!");
- }
- if (response == null)
- throw new Exception("服务器超时未响应");
- else if (response.Success)
- {
- Type returnType = targetMethod.ReturnType;
- if (returnType == typeof(void))
- return null;
- else
- return response.Data;
- }
- else
- throw new Exception($"服务器异常, 错误消息:{response.Msg}");
- }
- catch (Exception ex)
- {
- if (ex is TargetInvocationException)
- {
- LogException(ex.InnerException ?? ex, targetMethod);
- throw ex.InnerException ?? ex;
- }
- else
- {
- throw ex;
- }
- }
- }
- /// <summary>
- /// aop 异常的处理
- /// </summary>
- /// <param name="exception"></param>
- /// <param name="methodInfo"></param>
- private void LogException(Exception exception, MethodInfo methodInfo = null)
- {
- try
- {
- var errorMessage = new StringBuilder();
- errorMessage.AppendLine($"Class {methodInfo.IsAbstract.GetType().FullName}");
- errorMessage.AppendLine($"Method {methodInfo?.Name} threw exception");
- errorMessage.AppendLine(exception.Message);
- //_logError?.Invoke(errorMessage.ToString()); 记录到文件系统
- }
- catch (Exception)
- {
- // ignored
- //Method should return original exception
- }
- }
- View Code
这个类的源码与上一篇反向代理文章中所讲的核心区别是 Invoke 的实现, 上篇文章中其调用的是本地的一个类实体的方法, 本文中其调用的是远程服务中的类实体的方法
client 调用代码如下
- static void Main(string[] args)
- {
- //IHello client = RPCClientFactory.GetClient<IHello>("127.0.0.1", 39999);
- var serviceProxy = new ProxyDecorator<IHello>();
- IHello client = serviceProxy.Create("127.0.0.1", 39999, "IHello");
- client.SayHello("Hello");
- Console.WriteLine("完成");
- Console.ReadLine();
- }
重新启动 Server 和 Client 执行效果如下
和原作者的执行结果一致, 那么我们换个接口来试试: 创建 IMail 和 Mail 两个类, 并包含一个成员 string Send(string name)//IMail 和 Mail 的成员
- public string Send(string name)
- {
- Console.WriteLine(name);
- return $"你的名字是{name}";
- }
Client 端调用代码
- static void Main(string[] args)
- {
- //IHello client = RPCClientFactory.GetClient<IHello>("127.0.0.1", 39999);
- //var serviceProxy = new ProxyDecorator<IHello>();
- //IHello client = serviceProxy.Create("127.0.0.1", 39999, "IHello");
- var serviceProxy = new ProxyDecorator<IMail>();
- IMail client = serviceProxy.Create("127.0.0.1", 39999, "IMail");
- string msg= client.Send("张三丰");
- Console.WriteLine(msg);
- Console.WriteLine("完成");
- Console.ReadLine();
- }
服务端添加服务监控
- static void Main(string[] args)
- {
- RPCServer rPCServer = new RPCServer(39999);
- rPCServer.RegisterService<IHello, Hello>();
- rPCServer.RegisterService<IMail, Mail>();
- rPCServer.Start();
- Console.ReadLine();
- }
预计客户端输出:
你的名字是张三丰
完成
服务端输出是:
张三丰
我们先后启动 server 和 client 两个端来看看
至此动态代理的应用示例已经演示完毕.
在查看 寒空飞箭 Git 源码时候我们发现 RPCClientProxy 类和我们的 ProxyDecorator<T> 类 实现了相同的效果, 寒空飞箭的实现方式也是很令人振奋, 独辟蹊径, 非常值得学习. 下篇文章将会分析他的用法. 感兴趣的可以自行查看作者的源码.
参考文献:
https://www.cnblogs.com/coldairarrow/p/10193765.html
说明:
RPC 功能的实现是直接引用作者 寒空飞箭 的代码, 对此向 寒空飞箭 表示感谢
来源: https://www.cnblogs.com/netqq/p/11462054.html