RPCの呼び出し
1.発信者(クライアントクライアント)ローカルコールへのコールを開始するように、
前記クライアント・スタブ(クライアント・スタブ)通話料金を受信した後には、メソッド名とパラメータは、特定のフォーマット可能なネットワークにエンコードパッキン呼び出されますメッセージ本体の送信;
3.クライアント・スタブサーバにネットワークを介して送信されるメッセージの本体、
4スタブサーバ(サーバ・スタブ)デコーダをアンパックするための対応する形式、メソッド名とパラメータに従って、ネットワークを介してメッセージが受信された受信し、
5 。サーバ・スタブのメソッド名とローカルコールのパラメータに応じて、
; 6.呼び出し先(サーバ)、ローカルコールがスタブサーバーに結果を返す実行
7スタブサーバーは、メッセージパッケージに符号化された値を返し、クライアントにネットワークを介して送信します。
8.クライアント・スタブメッセージ、アンパックデコーダを受信した後、クライアントに返される。
9このクライアントRPCコールの最終結果。
参考https://www.cnblogs.com/FG123/p/10261676.html
参考https://www.jianshu.com/p/bb9beca7f7bc 第四四半期
第二に、RPC呼び出しの考え方(なぜ使用プロキシクラス)
私たちが見てきた都合の良いときにRPC、
この方法は、のgetService(と呼ばれていることを前提とし、あなたが関数を書く場合は、リモートメソッドへの毎回のサービス、パラメータ、およびその他の情報の名前は、サービスを通じて呼び出し、複数のサービスを呼び出すためのシステムを想定するとmethodNameの、オブジェクト[] 、パラメータ3、パラメータ4)
のは、この方法では、各消費者が結果を得ることができるように思わ呼ぶことにしましょう。
どこ各呼び出しは、リモートサービスはのgetServiceがあまりにも面倒ではありません渡すことができるようにクラスメソッド名とパラメータに関する追加情報を反映すべきですか?
私たちは、クライアントのgetServiceすべての変換結果を持って、同じタイプになりません各サービスのリモートサービスから返された結果を知るためには、あまりにも面倒ではないですか?
良い解決策はありませんか?
-プロキシクラスを使用して、我々は、プロキシクラス反射エージェントインタフェース(名・パラメータやその他)、リモートサービスへのリモート呼び出し、そして結果が得られた変換は、この方法の種々の特性を得ます。この方法では、見て、ほとんどのgetServiceまあ以上!そして、なぜ我々は、プロキシクラスを使用していますか?(私は知らないが、それがぶら下がっているように見えます。)これは非常に良いように見えるしていません。また、リモートサービスを呼び出すための複数のクラスが存在する場合、それはプロキシクラスを記述するために多くのことを意味するでしょうか?
思考:消費者はRPCの変換結果の種類に注意を呼び出す必要がないように、プロキシクラスにこの変換プロセスを使用したプロキシクラスの後、消費者に毎回変換結果をのgetServiceを呼び出した後。
だから、人々は、動的プロキシを発明 - 「呂氏は革命と」から
これは、各クラスがプロキシを書く必要があることが判明しました。今暁明は、直接に爆発し、1000年にプロキシクラスのプロジェクトに書くために!爆発します!
N-世代暁明の顧客調査の後と方法は非常に地方の努力にまとめることができることを見出しました。 - 動的プロキシ
簡単に言えば:動的プロキシクラスを作成します(https://www.cnblogs.com/netqq/p/11452374.html)、あなたは各コンシューマプロキシクラスを記述する必要はありませんので、そしてとてもクールではありません
第三に、動的プロキシとRPC
RPCの簡単な例を見つけるためにインターネット、初心者が学ぶことのために非常に適しhttps://github.com/Coldairarrow/DotNettyRPC
Serverプロジェクトを実行し、クライアントのプロジェクトを実行するプロジェクトをダウンロードした後、
こんにちは、別のサーバコンソールに文字列の出力を参照してください。これは、クライアントプログラムはIHello.SayHelloサーバ()サービス出力を呼び出すです。
私たちは、著者のクライアント呼び出しを見て
RPCClientFactoryソース次のように
名前空間Coldairarrow.DotNettyRPC { /// <要約> /// クライアント工場 /// </要約> パブリック クラスRPCClientFactory { プライベート 静的 ConcurrentDictionary < 文字列は、オブジェクト > _SERVICESは{ GET ;} = 新しい新ザ・ConcurrentDictionary < 文字列、オブジェクト > ( ); /// <要約> /// 取得し、クライアント /// 注:デフォルトのインタフェース名、サービス名 /// </要約> /// <typeParam名= "T"> インターフェース定義タイプ</ typeparam> /// <PARAM NAME = "SERVERIP"> リモートサービスIP </ param>の /// <PARAM NAME = "ポート"> リモートサービスポート</ param>の /// <返しA> </ Aが返されます> 公共の 静的 T getClient <T>(文字列 SERVERIP、int型ポート)T:クラス { 返す getClient <T>(SERVERIP、港、typeof演算(T).NAME); } /// <要約> /// クライアントを取得 /// 注:カスタムサービス名 /// </要約> /// <typeparam名= "T「> インターフェース定義タイプ </ typeparam> /// <PARAM NAME = "SERVERIP"> リモートサービスIP </ param>の /// <PARAM NAME = "ポート"> リモートサービスポート</ param>の /// <PARAM NAME = "serviceNameを" > サービス名</ param>の /// <戻り値> </戻り値> パブリック 静的 T getClient <T>(文字列 SERVERIP、int型ポート、文字列 serviceNameの)WHERE T:クラス { T-サービス = ヌル; 文字列のキー$ = "{serviceNameを} - {SERVERIP} - {ポート} 」。 試す { サービス = (T)_services [キー]。 } キャッチ { VAR clientProxy = 新しいRPCClientProxy { SERVERIP = SERVERIP、 のServerPort = ポート、 サービス種別 = typeof演算(T) のServiceName = serviceNameを }。 サービス = clientProxy.ActLike <T> (); // 动态代理? _services [キー] = サービス。 } 戻りサービス。 } } }
例では、プログラムはGetClientを呼び出します
実際に、動的に生成されたプロキシクラス、タイプIHelloのオブジェクトを返します。
のは、同じ効果を達成するために、当社独自の動的プロキシクラスで、プログラムの作者を脇に置くましょう。
ProxyDecorator <T>クラスDotNettyRPCプロジェクトを追加します。 ダウンロードSystem.Reflection.DispatchProxy.dllはnugetを必要とします
あなたは問題が発生し、ProxyDecoratorを追加し、時間をコンパイル、我々は、サーバー、クライアントのプロジェクトとDotNettyRPCはので、適切に実行に動作するNETCOREプロジェクトになります System.Reflection.DispatchProxy.dllのみNETCOREクラスライブラリは、NET Frameworkのプロジェクトをサポートしていません。
ProxyDecorator <T>ソース
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功能的实现是直接引用作者 寒空飞箭 的代码,对此向 寒空飞箭 表示感谢