Reactive Extensionsを使用した非同期ネットワークプログラミング


25

socket年か前に(多かれ少なかれ)「低レベル」非同期プログラミング(イベントベースの非同期パターン(EAP)方式)を行い、最近TcpListener(非同期プログラミングモデル(APM))に「上」に移動した後、async/await(Task-based Asynchronous Pattern (TAP))に移動しようとしています だから私は考えていた。私の問題のドメインによりぴったりと合うかもしれないのでRX、なぜ行ってみてください(Reactive Extensions)。

多くのクライアントがTcpを介してアプリケーションに接続し、双方向(非同期)通信を開始する多くのコードを作成する必要があります。クライアントまたはサーバーはいつでもメッセージを送信する必要があると判断する場合があります。そのため、これは従来のrequest/responseセットアップではなく、リアルタイムで双方向の「回線」であり、両者が自由に送信できます、いつでも好きなときに。(これを説明するためのまともな名前を持っている人がいれば、それを聞いてうれしいです!)。

「プロトコル」はアプリケーションごとに異なります(私の質問にはあまり関係ありません)。ただし、最初の質問があります。

  1. 1つの「サーバー」のみが実行されていることを考えると、多くの(通常は数千の)接続(クライアントなど)を追跡する必要があります。州など、どのアプローチを好むでしょうか?EAP / TAP / APM?RXもオプションと見なされますか?そうでない場合、なぜですか?

だから、a)それは要求/応答プロトコルではないので、非同期を動作させる必要があるので、スレッド/クライアントを「メッセージ待ち」ブロッキング呼び出しまたは「メッセージ送信」ブロッキング呼び出し(ただし、送信がそのクライアントのみをブロックできます(私はそれと一緒に暮らすことができます)およびb)多くの同時接続を処理する必要があります。ブロッキング呼び出しを使用してこれを(確実に)行う方法はありません。

私のアプリケーションのほとんどはVoiP関連です。SIP cientsからのSIPメッセージでも、FreeSwitch / OpenSIPSなどのアプリケーションからのPBX(関連)メッセージングでも、最も簡単な形式では、多くの「チャット」クライアントを処理しようとする「チャット」サーバーを想像できます。ほとんどのプロトコルはテキストベース(ASCII)です。

したがって、前述の手法のさまざまな順列を実装した後、単純にインスタンス化できるオブジェクトを作成して作業を簡素化し、IPEndpoint聞きたいものを伝え、興味のあることが起こったらいつでも教えてもらいたい(通常はイベントを使用するため、一部のEAPは通常、他の2つの手法と混合されます)。クラスは、プロトコルを「理解」しようとする必要はありません。単に着信/発信文字列を処理する必要があります。したがって、RXに注目して(最終的に)作業を簡素化することを期待して、ゼロから新しい「フィドル」を作成しました。

using System;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Sockets;
using System.Reactive.Linq;
using System.Text;

class Program
{
    static void Main(string[] args)
    {
        var f = new FiddleServer(new IPEndPoint(IPAddress.Any, 8084));
        f.Start();
        Console.ReadKey();
        f.Stop();
        Console.ReadKey();
    }
}

public class FiddleServer
{
    private TcpListener _listener;
    private ConcurrentDictionary<ulong, FiddleClient> _clients;
    private ulong _currentid = 0;

    public IPEndPoint LocalEP { get; private set; }

    public FiddleServer(IPEndPoint localEP)
    {
        this.LocalEP = localEP;
        _clients = new ConcurrentDictionary<ulong, FiddleClient>();
    }

    public void Start()
    {
        _listener = new TcpListener(this.LocalEP);
        _listener.Start();
        Observable.While(() => true, Observable.FromAsync(_listener.AcceptTcpClientAsync)).Subscribe(
            //OnNext
            tcpclient =>
            {
                //Create new FSClient with unique ID
                var fsclient = new FiddleClient(_currentid++, tcpclient);
                //Keep track of clients
                _clients.TryAdd(fsclient.ClientId, fsclient);
                //Initialize connection
                fsclient.Send("connect\n\n");

                Console.WriteLine("Client {0} accepted", fsclient.ClientId);
            },
            //OnError
            ex =>
            {

            },
            //OnComplete
            () =>
            {
                Console.WriteLine("Client connection initialized");
                //Accept new connections
                _listener.AcceptTcpClientAsync();
            }
        );
        Console.WriteLine("Started");
    }

    public void Stop()
    {
        _listener.Stop();
        Console.WriteLine("Stopped");
    }

    public void Send(ulong clientid, string rawmessage)
    {
        FiddleClient fsclient;
        if (_clients.TryGetValue(clientid, out fsclient))
        {
            fsclient.Send(rawmessage);
        }
    }
}

public class FiddleClient
{
    private TcpClient _tcpclient;

    public ulong ClientId { get; private set; }

    public FiddleClient(ulong id, TcpClient tcpclient)
    {
        this.ClientId = id;
        _tcpclient = tcpclient;
    }

    public void Send(string rawmessage)
    {
        Console.WriteLine("Sending {0}", rawmessage);
        var data = Encoding.ASCII.GetBytes(rawmessage);
        _tcpclient.GetStream().WriteAsync(data, 0, data.Length);    //Write vs WriteAsync?
    }
}

この「フィドル」には、実装固有の詳細が少しあります。この場合、私はFreeSwitch ESLを使用し"connect\n\n"ているので、より一般的なアプローチにリファクタリングするとき、フィドルを削除する必要があります。

また、匿名メソッドをServerクラスのプライベートインスタンスメソッドにリファクタリングする必要があることも認識しています。OnSomethingメソッド名に使用する規則(たとえば " "など)がわからないだけですか。

これが私の基礎/出発点/基盤です(これには「調整」が必要です)。これについていくつか質問があります。

  1. 上記の質問「1」を参照してください
  2. 私は正しい軌道に乗っていますか?または、私の「設計」の決定は不当ですか?
  3. 同時実行性:これは数千のクライアントに対応しますか(実際のメッセージは別として解析/処理します)
  4. 例外について:クライアント内で発生した例外をサーバーに「アップ」する方法(「RXワイズ」)を取得する方法がわかりません。良い方法は何でしょうか?
  5. クライアントを何らかのClientId方法で公開し、それらのメソッドを直接呼び出すことを前提に、サーバークラスから接続されたクライアントを取得できます(それを使用)。また、Serverクラスを介してメソッドを呼び出すこともできます(たとえば、Send(clientId, rawmessage)メソッド(後者のアプローチは、反対側にメッセージをすばやく取得するための「便利な」メソッドです)。
  6. 私はここからどこに(そしてどのように)行くべきかよくわかりません:
    • a)着信メッセージを処理する必要があります。これをどのように設定しますか?もちろんストリームを取得できますが、受信したバイトの取得はどこで処理しますか?何らかの種類の「ObservableStream」が必要だと思います-購読できるものですか?これをFiddleClientor に入れFiddleServerますか?
    • b)これらのFiddleClient/ FiddleServerクラスがより具体的に実装されるまでイベントを使用したくないと仮定すると、より具体的なFooClient/ FooServerクラスを使用してアプリケーション固有のプロトコル処理などを調整します:基になる「Fiddle」クラスのデータを取得する方法より具体的な対応物?

すでに読んだ記事/リンク/スキミング/参照用:


既存のを見てみましょうReactiveSocketsのライブラリ
Flagbug

2
ライブラリやリンクを探しているわけではありませんが(参考のために感謝します)、質問や一般的な設定に関する入力/アドバイス/ヘルプを探しています。私は自分のコードを改善し、これをどの方向に取り入れるか、賛否両論を比較検討することをもっとうまくできるようになりたいと思っています。この経験から学び、Rx /ネットワークプログラミングの経験を増やしたいです。
RobIII 14年

ライブラリはオープンソースであるため確かに、しかし、あなたはそこに実装その方法を見ることができます
Flagbug

1
確かに、ソースコードを見ても、設計上の決定が行われた理由が説明されていません。また、ネットワークプログラミングと組み合わせてRxを使用するのは比較的新しいため、このライブラリが適切かどうか、設計が理にかなっているか、適切な決定が下されたか、それが適切かどうかを判断するのに十分な経験がありません。
RobIII 14年

サーバーをハンドシェイクのアクティブなメンバーとして再考するとよいと思うので、そのサーバーは接続をリッスンする代わりに接続を開始します。たとえば、こちら:codeproject.com/Articles/20250/Reverse-Connection-Shell

回答:


1

...単純にインスタンス化できるオブジェクトを作成し、どのIPEndpointでリッスンするかを指定して、興味のあることが起こったときに通知するようにすることで、作業を単純化したい...

その声明を読んだ後、私はすぐに「俳優」と考えました。アクターはオブジェクトに非常に似ていますが、(オブジェクトのメソッドを直接呼び出すのではなく)メッセージを渡す入力が1つだけであり、非同期で動作する点が異なります。非常に簡単な例では、アクターを作成し、IPEndpointおよび結果を送信するアクターのアドレスを含むメッセージを送信します。消灯し、バックグラウンドで動作します。「興味のあること」が起こったときだけ、あなたはそれから返事をします。負荷を処理するために必要な数のアクターをインスタンス化できます。

いくつかのアクターライブラリがあることは知っていますが、.Netのアクターライブラリには慣れていません。私はTPL Dataflowライブラリに精通しており(私の本http://DataflowBook.comでそれをカバーするセクションがあります)、そのライブラリで簡単なアクターモデルを簡単に実装できるはずです。


おもしろそうです。おもちゃプロジェクトをセットアップして、自分に合っているかどうかを確認します。提案をありがとう。
RobIII
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.