同期/非同期クライアントサーバーを記述します

みなさんこんにちは。



この記事では、同期呼び出しと非同期呼び出しを実装し、プロシージャへのアクセスを区別し、データを圧縮するマルチスレッドTCPアプリケーションサーバーの原理を検討します。



それがすべて始まった方法。

それはすべて、インターネットを介して動作する複数の異なるアプリケーション間の相互作用の実装を開始する場所の難しい選択から始まりました。 WCFはそのような問題を解決できるように見えますが、残念ながら、欠点やいくつかの問題がないわけではなく、その動作原理はデータ転送速度に大きく影響します。 シンプルなソリューションが必要であり、同時に非常に機能的でした。



プロジェクトは、さまざまなタイプのアプリケーションのプロシージャと関数のインターフェイスを作成するクラスライブラリで開始します。

public interface ICommon { string[] GetAvailableUsers(); void ChangePrivileges(string Login, string password); } public interface IDog { bool TryFindObject(out object obj); int Bark(int nTimes); } public interface ICat { void CutTheText(ref string Text); }
      
      





ここでは、3つの異なるアクセスレベルの手順の署名について説明しました。 ご想像のとおり、Commonにはすべてのタイプのリモートクライアントで利用可能な手順が含まれています。 ここでは犬と猫、これらは2種類のリモートクライアントです。それぞれの手順は自分だけが利用できます。



ここで、サーバーとクライアント間でデータを交換するクラスを作成します。

 [Serializable] public class Message { public Message(string Command, object[] Parameters) { this.Command = Command; if (Parameters != null) this.prms = Parameters; } public bool IsSync; public bool IsEmpty = true; public readonly string Command; public object ReturnValue; public object[] prms; public Exception Exception; }
      
      





お客様


クライアントは、インターフェイスメソッドとサーバー間のプロキシ接続を実装します。 これを行うには、プロキシを実装するクラスを作成します。

 private class Proxy<T> : RealProxy where T : class { UniservClient client; public Proxy(UniservClient client): base(typeof(T)) { this.client = client; } public override IMessage Invoke(IMessage msg) { IMethodCallMessage call = (IMethodCallMessage)msg; object[] parameters = call.Args; int OutArgsCount = call.MethodBase.GetParameters().Where(x => x.IsOut).Count(); Message result = client.Execute(call.MethodName, parameters); parameters = parameters.Select((x, index) => result.prms[index] ?? x).ToArray(); return new ReturnMessage(result.ReturnValue, parameters, OutArgsCount, call.LogicalCallContext, call); } }
      
      





インターフェイスメソッドにアクセスするためのプロパティを作成します。

 public ICommon Common { get; private set; } public IDog Dog { get; private set; } public ICat Cat { get; private set; }
      
      





プロキシとプロパティを初期化します。

 CommonProxy = new Proxy<ICommon>(this); DogProxy = new Proxy<IDog>(this); CatProxy = new Proxy<ICat>(this); Common = (ICommon)CommonProxy.GetTransparentProxy(); Dog = (IDog)DogProxy.GetTransparentProxy(); Cat = (ICat)CatProxy.GetTransparentProxy();
      
      





サーバーコマンドの処理:

 private void Listener() { while (true) { try { if (ListenerToken.IsCancellationRequested) return; if (!IsConnected) _Connect(); while (true) { if (ListenerToken.IsCancellationRequested) return; Message msg = ReceiveData<Message>(); if (msg.Command == "OnPing") { //   SendData(msg); if (Events.OnPing != null) Events.OnPing.BeginInvoke(null, null); continue; } if (msg.IsSync) { //     SyncResult(msg); } else { //    try { //   Action var pi = typeof(IEvents).GetProperty(msg.Command, BindingFlags.Instance | BindingFlags.Public); if (pi == null) throw new Exception(string.Concat(" \"", msg.Command, "\"  ")); var delegateRef = pi.GetValue(this, null) as Delegate; //   if (delegateRef != null) ThreadPool.QueueUserWorkItem(state => delegateRef.DynamicInvoke(msg.prms)); } catch (Exception ex) { throw new Exception(string.Concat("    \"", msg.Command, "\""), ex); } } } } catch (TaskCanceledException) { return; } catch (Exception ex) { if (Events.OnError != null) Events.OnError.BeginInvoke(ex, null, null); } finally { _Dicsonnect(); } Thread.Sleep(2000); } }
      
      





次のメソッドは、リモートプロシージャの実行を担当します。

 private Message Execute(string MethodName, object[] parameters) { lock (syncLock) { _syncResult = new Message(MethodName, parameters); _syncResult.IsSync = true; _OnResponce.Reset(); SendData(_syncResult); _OnResponce.Wait(); //    if (_syncResult.IsEmpty) {//  ,    throw new Exception(string.Concat("      \"", MethodName, "\"")); } if (_syncResult.Exception != null) throw _syncResult.Exception; //    return _syncResult; } } private void SyncResult(Message msg) { //     _syncResult = msg; _syncResult.IsEmpty = false; _OnResponce.Set(); //   }
      
      





一般的に、クライアントは次のように機能します。

アプリケーションがインターフェイスメソッドを呼び出すと、実行はプロキシサーバーに進みます。 プロキシは、呼び出されたメソッドの名前とそのパラメーターをサーバーに送信し、実行結果を見越してストリームをブロックします。 サーバーの応答は別のスレッドで解析され、ブロックされたスレッドを続行できます。



サーバー


特定の機能の可用性を決定するクラスの階層を作成しましょう。

 public class Cat_Ring0 : Ring2, ICat { public Cat_Ring0(User u) : base(u) { up.UserType = UserType.Cat; } public void CutTheText(ref string Text) { Text = Text.Remove(Text.Length - 1); } } public class Dog_Ring0 : Dog_Ring1, IDog { public Dog_Ring0(User u) : base(u) { up.UserType = UserType.Dog; } public int Bark(int nTimes) { var ConnectedDogs = ConnectedUsers.ToArray().Where(x => x.UserType == UserType.Dog).Select(x => x.nStream); ConnectedDogs.AsParallel().ForAll(nStream => { SendMessage(nStream, new Message("OnBark", new object[] { nTimes})); }); return ConnectedDogs.Count(); } } public class Dog_Ring1 : Ring2 { public Dog_Ring1(User u): base(u) { up.UserType = UserType.Dog; } public bool TryFindObject(out object obj) { obj = "TheBall"; return true; } } public class Ring2 : Ring, ICommon { public Ring2(User u) : base(u) { } public string[] GetAvailableUsers() { return new string[] { "Dog0", "Dog1", "Tom" }; } public void ChangePrivileges(string Animal, string password) { switch (Animal) { case "Dog0": if (password != "groovy!") throw new Exception("  "); up.ClassInstance = new Dog_Ring0(up); break; case "Dog1": if (password != "_password") throw new Exception("  "); up.ClassInstance = new Dog_Ring1(up); break; case "Tom": if (password != "TheCat") throw new Exception("  "); up.ClassInstance = new Cat_Ring0(up); break; default: throw new Exception("   "); } } } public abstract class Ring { public readonly User up; public Ring(User up) { this.up = up; } }
      
      





これで、対応するクライアントがアクセスできるように、特定の「リング」にプロシージャを配置するだけで十分です。 Ring0はアクセスの最上位レベルであり、このタイプのユーザーは、その中のプロシージャだけでなく、継承されたすべてのクラスのプロシージャにもアクセスできます。 最初は、ユーザーはRing2になります。これは、すべてのユーザーが使用できる一般的なメソッドのみを実装します。 次に、 ChangePrivileges()を使用してユーザーはログイン後、特定のアクセスレベルで特定の種類の「リング」にアクセスできます。



メインサーバーの操作は、次の方法に限定されます。

 private void ProcessMessage(Message msg, User u) { string MethodName = msg.Command; if (MethodName == "OnPing") return; //        MethodInfo method = u.RingType.GetMethod(MethodName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); try { if (method == null) { throw new Exception(string.Concat(" \"", MethodName, "\" ")); } try { //    msg.ReturnValue = method.Invoke(u.ClassInstance, msg.prms); } catch (Exception ex) { throw ex.InnerException; } //  ref  out  msg.prms = method.GetParameters().Select(x => x.ParameterType.IsByRef ? msg.prms[x.Position] : null).ToArray(); } catch (Exception ex) { msg.Exception = ex; } finally { //     SendMessage(u.nStream, msg); } }
      
      





ClassInstanceプロパティには、プロシージャが名前で検索される「リング」のインスタンスが含まれます。



実行ログの例:

画像



結果は、WCFに似たシンプルでエレガントなソリューションです。

ここでソースを取得できます



All Articles