何socket
年か前に(多かれ少なかれ)「低レベル」非同期プログラミング(イベントベースの非同期パターン(EAP)方式)を行い、最近TcpListener
(非同期プログラミングモデル(APM))に「上」に移動した後、async/await
(Task-based Asynchronous Pattern (TAP))に移動しようとしています だから私は考えていた。私の問題のドメインによりぴったりと合うかもしれないのでRX
、なぜ行ってみてください(Reactive Extensions)。
多くのクライアントがTcpを介してアプリケーションに接続し、双方向(非同期)通信を開始する多くのコードを作成する必要があります。クライアントまたはサーバーはいつでもメッセージを送信する必要があると判断する場合があります。そのため、これは従来のrequest/response
セットアップではなく、リアルタイムで双方向の「回線」であり、両者が自由に送信できます、いつでも好きなときに。(これを説明するためのまともな名前を持っている人がいれば、それを聞いてうれしいです!)。
「プロトコル」はアプリケーションごとに異なります(私の質問にはあまり関係ありません)。ただし、最初の質問があります。
- 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」を参照してください
- 私は正しい軌道に乗っていますか?または、私の「設計」の決定は不当ですか?
- 同時実行性:これは数千のクライアントに対応しますか(実際のメッセージは別として解析/処理します)
- 例外について:クライアント内で発生した例外をサーバーに「アップ」する方法(「RXワイズ」)を取得する方法がわかりません。良い方法は何でしょうか?
- クライアントを何らかの
ClientId
方法で公開し、それらのメソッドを直接呼び出すことを前提に、サーバークラスから接続されたクライアントを取得できます(それを使用)。また、Serverクラスを介してメソッドを呼び出すこともできます(たとえば、Send(clientId, rawmessage)
メソッド(後者のアプローチは、反対側にメッセージをすばやく取得するための「便利な」メソッドです)。 - 私はここからどこに(そしてどのように)行くべきかよくわかりません:
- a)着信メッセージを処理する必要があります。これをどのように設定しますか?もちろんストリームを取得できますが、受信したバイトの取得はどこで処理しますか?何らかの種類の「ObservableStream」が必要だと思います-購読できるものですか?これを
FiddleClient
or に入れFiddleServer
ますか? - b)これらの
FiddleClient
/FiddleServer
クラスがより具体的に実装されるまでイベントを使用したくないと仮定すると、より具体的なFooClient
/FooServer
クラスを使用してアプリケーション固有のプロトコル処理などを調整します:基になる「Fiddle」クラスのデータを取得する方法より具体的な対応物?
- a)着信メッセージを処理する必要があります。これをどのように設定しますか?もちろんストリームを取得できますが、受信したバイトの取得はどこで処理しますか?何らかの種類の「ObservableStream」が必要だと思います-購読できるものですか?これを
すでに読んだ記事/リンク/スキミング/参照用: