1

私は、中央サーバーへの通信に raw TCP ソケットを使用するクライアント アプリケーションに取り組んでいます。アプリケーション メッセージはシリアル化され、TCP ストリームに渡されるフレームを作成するために長さがプレフィックスされます。

これを処理するための古典的な方法の 1 つは、ソケット クラスで Receive または BeginReceive を直接呼び出し、コールバックでメッセージを逆シリアル化し、メッセージを別のスレッドで処理するために別のキューに渡し、コールバックにソケットで別の受信を再度開始させることです。

このアプローチの素朴な実装は私にとって理想的ではありません.メッセージのシリアライゼーションとデシリアライゼーションをソケットに密接に結合し、キューが異なるスレッド/コールバックでうまく機能するようにするにはかなりの「配管」が必要です. また、これはやや漏れやすい抽象化でもあります。呼び出し元のコードには、入力メッセージと出力メッセージの「データ フロー」ではなく、基礎となるソケットの知識が必要です。

完全に .NET 4.5 内で作業していることを考えると、TPL (TaskFactory.FromAsync) を使用してソケットの Begin および End 非同期メソッドをラップすることは当然の選択です。ただし、いくつかの理由により、この時点からどのように進めればよいかわかりません。

  1. データを受信するために完了しない非同期の「タスク」が必要です。ソケットが接続されている限り、メッセージのストリームを処理したいと思います。中断 (切断、ソケット エラー、またはキャンセル要求) は、従来のタスクの完了ではなく、例外になります。Stephen Toub ( http://blogs.msdn.com/b/pfxteam/archive/2011/10/02/10218999.aspx ) によると、私は常にタスクを完了する必要があります。これにより、少し問題が生じます。従来の意味では、ソケットの受信は決して完了しません。Stephen は、「Awaiting Socket Operations」の投稿で少し矛盾しているように見えます。そこでは、ソケット エラーなしでは決して完了しないソケット読み取りを示しています ( http://blogs.msdn.com/b/pfxteam/archive/2011/12/15 /10248293.aspx )。
  2. 送信するデータを同期的に「キューに入れる」方法が必要です。発信者は、送信されるメッセージをブロックせずに送信できる必要があり、メッセージはソケットを介して順次送信される必要があります。つまり、メッセージのフレーミングにより、ソケット自体で一度に 1 つの送信のみが行われます。TPL データフローは適していますか、それとも別のキューイング パターンを使用する必要がありますか?
  3. メッセージのシリアル化とメッセージの送信の間の懸念を明確に分離したいと思います。

このタイプの戦略の例はあまり見たことがなく、「直接的な」ソケット I/O または単純な実装のみです。私の直感では、シリアライゼーションとデシリアライゼーションをパイプライン処理できることを考えると、TPL Dataflow が適していることがわかります。

Receive タスクの効果的に無限のチェーンを TPL Dataflow などに橋渡しする方法がわかりません。

何か案は?

4

1 に答える 1

3

現時点では、これに対する適切なアプローチの例はありません。ソリューションが機能するようになったら、どこかで公開することをお勧めします。

1.データを受信するために完了しない非同期の「タスク」が必要です...

「従来の意味では、ソケット受信は決して完了しない」という記述には同意しません。低レベルの「受信」操作 (たとえば、/または Toub の のFromAsyncラッパー) は、利用可能なデータがある場合、ソケットが閉じられている場合、またはエラーが発生した場合に完了する を返します。高レベルの「レシーバー」操作 ( Toub の投稿など) は、ソケットが閉じられるか、エラーが発生したときに完了します。BeginReceiveEndReceiveReceiveAsyncTaskReadAsync

完了するまでに無期限の時間がかかる可能性がある s を持つことができTaskますが、完了する限り問題ありませんこの投稿で Toub が指摘していたのは、 s は最終的にTask完了する必要があるということです(特にエラー状況の処理)。これは、データを生成せず、決して終了しないオブザーバブルを持つことが完全に有効である Rx のアプローチとは異なります。

2.送信するデータを同期的に「キューに入れる」方法が必要です...

技術的には、「同期」ではなく「シリアル化」(「順序どおり」)です。理想的なソリューションは、非同期でシリアライズすることです。これにはいくつかのアプローチがあります。

TPL Dataflow はぴったりだと思います。実際には、ソケットごとに 2 つの論理「ストリーム」があることに注意してください (読み取りと書き込みは独立しています)。私は Dataflow ベースのソケット ラッパーである程度の成功を収めましたが、製品品質にする時間がありませんでした。2 つのストリームがあるため、API はかなり厄介です (プラグイン可能な「ブロック」のそれぞれに、入力用と出力用の 2 つの TPL データフロー ブロックが必要です)。

別のアプローチはRxです。Rx はプレーンな s や TPL Dataflow よりも学習曲線が高くなりTaskますが、かなりのパワー (および効率) を提供します。数年前に Rx ベースのソケットをいじりましたが、何も機能しませんでした。最近のRxのドキュメントと例ははるかに優れているため、今日では実行可能なオプションと考えています.

asyncメソッドを直接使用するだけのアプローチもあります。キューの代わりに同期を使用します。たとえば、 aを使用して、特定のソケットに対して一度SemaphoreSlimに 1 つのメソッドのみを実行できるようにすることができます。SendAsyncただし、それはセマンティクスを変更し、「シリアライゼーションネス」の一部を呼び出しコードにプッシュします。単純なエンキューおよび完了タスク (送信スロットリングにヒットしない限り、常に同期的に完了する) の代わりに、非同期的に送信を待機してから完了するタスク (ほとんどの場合、非同期的に待機します)。asyncプロデューサー/コンシューマー キュー (私が書いたものなど)を構築することでこれを軽減できますが、のコンシューマーがあります。Task追跡する必要があり、その時点で TPL データフローを書き直しています。

受信タスクの事実上「無限の」チェーンをTPLデータフローまたは同様のものに「ブリッジ」する方法がわかりません。

これを行うための適切な組み込みの方法はありません。

Task簡単な解決策は、データを TPL Dataflow パイプラインにプッシュすることだけを担当する「レシーバー」を用意することです。ただし、何らかのエラーが発生した場合にパイプラインが放棄されないように監視するTask必要があり、パイプラインを完全にシャットダウンする方法が必要です。

FuncBlockこの状況を処理する型を作成しました(ソケットの読み取りやデータフローへのその他の I/O ベースの入力に使用するという考えで)。メソッドと Dataflow ブロックがどのように相互作用するか (特にキャンセル/エラー/完了の周り) のすべてのセマンティクスを解決するのにしばらく時間がかかりましたが、async役立つと思います。フィードバックをいただければ幸いです。

于 2013-06-25T21:10:56.493 に答える