A call on the RPC
1. The caller (client Client) so as to initiate a call to a local call;
2. Client Stub (client stub) after receiving the call charge to be invoked method name and parameters packing encoded into a specific format capable network transmission of the message body;
3. Client stub the message body sent over the network to the server;
4. stub Server (server stubs) for unpacking decoder receives a message is received via a network according to the corresponding format, the method name and parameters;
5 . Server stub according to the method name and parameters of a local call;
6. callee (Server) performs a local call will return the result to the Stub Server;
7. the Stub Server returns a value encoded into the message package and send through the network to the client;
8. Client stub after receiving the message, the unpacking decoder, is returned to the Client;
9. the final result of this Client RPC calls.
Reference https://www.cnblogs.com/FG123/p/10261676.html
Reference https://www.jianshu.com/p/bb9beca7f7bc fourth quarter
Second, the way of thinking about the RPC call (Why use proxy class)
RPC at convenience we have seen,
Assuming the system to call multiple services, if you write a function, every time the name of the service, parameters, and other information to a remote method calls through a service, assume that this method is called getService ( methodName , Object [] , parameter 3 , parameter 4 )
Let's call each consumer in this method seems to be able to get results.
Where each call remote services should be reflected additional information about the class method name and parameters to be able to pass getService is not too much trouble?
To know the results returned by a remote service for each service will not be the same type that we have every conversion result getService the client, is not too much trouble?
There is no good solution?
- Use a proxy class, we get various properties of this method in the proxy class reflection agent interface (name & parameters & other), remote call to a remote service, and converting the results obtained. This method looks and above getService Well almost! Then why are we using a proxy class it? ( I do not know, but it looks like it is hanging. ) This does not seem like a very good Moreover if there are multiple classes to invoke a remote service, it would mean a lot to write proxy class?
Thoughts: After calling getService every time the conversion result in the consumer, after the proxy class using this conversion process into a proxy class, so that the consumer would not have to call attention to the type of the result of the conversion of RPC.
So people invented a dynamic proxy - from "Mr. Lu said revolution"
It was found that each class should write proxy. Now Xiao Ming to write in the proxy class project in 1000, directly explode on! Explode!.
After the N-generation Xiaoming customer research and found that a method can be summed up in a very provincial effort. - Dynamic Proxy
Simply put: create dynamic proxy class ( https://www.cnblogs.com/netqq/p/11452374.html ), so you do not have to write to each consumer a proxy class, and is not so cool
Third, the dynamic proxy and RPC
The Internet to find a simple example of RPC, very suitable for beginners to learn https://github.com/Coldairarrow/DotNettyRPC
After downloading the project to run Server project, and then run the client project
Hello see the output of the string on another server console. This is a client program calls IHello.SayHello server (the) service output.
We look at the author's client calls
RPCClientFactory source as follows
namespace Coldairarrow.DotNettyRPC { /// <Summary> /// client factory /// </ Summary> public class RPCClientFactory { Private static The ConcurrentDictionary < String , Object > _SERVICES { GET ;} = new new The ConcurrentDictionary < String , Object > ( ); /// <Summary> /// Get client /// Note: The default interface name service name /// </ Summary> /// <typeParam name = "T"> interface definition type</ typeparam> /// <param name = "ServerIP"> Remote Service IP </ param> /// <param name = "Port"> Remote Service Port </ param> /// <returns A> </ returns A> public static T getClient <T> ( String ServerIP, int Port) WHERE T: class { return getClient <T> (ServerIP, Port, typeof (T) .Name); } /// <Summary> /// Get client /// Note: The custom service name /// </ the Summary> /// <typeparam name = "T "> interface definition type</ typeparam> /// <param name = "ServerIP"> Remote Service IP </ param> /// <param name = "Port"> Remote Service Port </ param> /// <param name = "serviceName" > service name </ 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; } } }
In the example, the program calls the GetClient
Actually dynamically generated proxy class, returns an object of type IHello.
Let's put aside the author of the program, with our own dynamic proxy class to achieve the same effect.
Add ProxyDecorator <T> class DotNettyRPC project. Download System.Reflection.DispatchProxy.dll need nuget
You encounter problems and add ProxyDecorator compile time, we will server, client projects and DotNettyRPC into NETCORE project to work properly executed because System.Reflection.DispatchProxy.dll only NETCORE class library does not support NET Framework Project
ProxyDecorator <T> source
1 public class ProxyDecorator<T> : DispatchProxy 2 { 3 public string ServerIp { get; set; } 4 public int ServerPort { get; set; } 5 public string ServiceName { get; set; } 6 static Bootstrap _bootstrap { get; } 7 static ClientWait _clientWait { get; } = new ClientWait(); 8 9 static ProxyDecorator() 10 { 11 _bootstrap = new Bootstrap() 12 .Group(new MultithreadEventLoopGroup()) 13 .Channel<TcpSocketChannel>() 14 .Option(ChannelOption.TcpNodelay, true) 15 .Handler(new ActionChannelInitializer<ISocketChannel>(channel => 16 { 17 IChannelPipeline pipeline = channel.Pipeline; 18 pipeline.AddLast("framing-enc", new LengthFieldPrepender(8)); 19 pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(int.MaxValue, 0, 8, 0, 8)); 20 21 pipeline.AddLast(new ClientHandler(_clientWait)); 22 })); 23 } 24 25 public ProxyDecorator() 26 { 27 28 } 29 30 ///// <summary> 31 ///// 创建代理实例 32 ///// </summary> 33 ///// <param name="decorated">代理的接口类型</param> 34 ///// <returns></returns> 35 public T Create(string serverIp, int port, string serviceName) 36 { 37 38 object proxy = Create<T, ProxyDecorator<T>>(); //调用DispatchProxy 的Create 创建一个新的T 39 ((ProxyDecorator<T>)proxy).ServerIp = serverIp; 40 ((ProxyDecorator<T>)proxy).ServerPort = port; 41 ((ProxyDecorator<T>)proxy).ServiceName = serviceName; 42 return (T)proxy; 43 } 44 45 protected override object Invoke(MethodInfo targetMethod, object[] args) 46 { 47 if (targetMethod == null) throw new Exception("无效的方法"); 48 49 try 50 { 51 52 ResponseModel response = null; 53 IChannel client = null; 54 try 55 { 56 client = AsyncHelpers.RunSync(() => _bootstrap.ConnectAsync($"{ServerIp}:{ServerPort}".ToIPEndPoint())); 57 } 58 catch 59 { 60 throw new Exception("连接到服务端失败!"); 61 } 62 if (client != null) 63 { 64 _clientWait.Start(client.Id.AsShortText()); 65 RequestModel requestModel = new RequestModel 66 { 67 ServiceName = ServiceName, 68 MethodName = targetMethod.Name, 69 Paramters = args.ToList() 70 }; 71 var sendBuffer = Unpooled.WrappedBuffer(requestModel.ToJson().ToBytes(Encoding.UTF8)); 72 73 client.WriteAndFlushAsync(sendBuffer); 74 var responseStr = _clientWait.Wait(client.Id.AsShortText()).ResponseString; 75 response = responseStr.ToObject<ResponseModel>(); 76 } 77 else 78 { 79 throw new Exception("连接到服务端失败!"); 80 } 81 82 if (response == null) 83 throw new Exception("服务器超时未响应"); 84 else if (response.Success) 85 { 86 Type returnType = targetMethod.ReturnType; 87 if (returnType == typeof(void)) 88 return null; 89 else 90 return response.Data; 91 } 92 else 93 throw new Exception($"服务器异常,错误消息:{response.Msg}"); 94 95 } 96 catch (Exception ex) 97 { 98 if (ex is TargetInvocationException) 99 { 100 LogException(ex.InnerException ?? ex, targetMethod); 101 throw ex.InnerException ?? ex; 102 } 103 else 104 { 105 throw ex; 106 } 107 } 108 } 109 110 111 /// <summary> 112 /// aop异常的处理 113 /// </summary> 114 /// <param name="exception"></param> 115 /// <param name="methodInfo"></param> 116 private void LogException(Exception exception, MethodInfo methodInfo = null) 117 { 118 try 119 { 120 var errorMessage = new StringBuilder(); 121 errorMessage.AppendLine($"Class {methodInfo.IsAbstract.GetType().FullName}"); 122 errorMessage.AppendLine($"Method {methodInfo?.Name} threw exception"); 123 errorMessage.AppendLine(exception.Message); 124 125 //_logError?.Invoke(errorMessage.ToString()); 记录到文件系统 126 } 127 catch (Exception) 128 { 129 // ignored 130 //Method should return original exception 131 } 132 }
这个类的源码与上一篇反向代理文章中所讲的核心区别是 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功能的实现是直接引用作者 寒空飞箭 的代码,对此向 寒空飞箭 表示感谢