1

サーバーがクライアントとして機能できるようにする ASIO を使用してアプリケーションを作成しようとしています。例えば:

相互に通信する必要がある 3 つのサーバーがあります。ネットワーク内の他のサーバーと通信するときに、クライアントとして機能できる必要があります。3 つのサーバーはすべて、UNIX ドメイン ソケットまたは SSL を使用した TCP/IP を介して要求を処理できます。

データの流れは次のとおりです。

1) スタンドアロン クライアントが (UNIX ドメイン ソケット経由で) サーバー A に接続し、要求を送信します。

2) サーバーは要求に応答しようとしますが、応答できない場合は、サーバー B への TCP/IP 接続を開始し (サーバー A はサーバー B のクライアントとして機能します)、要求をサーバー B に転送します。また、サーバーはパケットを「汚染」して、サーバー B にメッセージを別のサーバーに転送しないように指示し、無限ループが作成されないようにします。

3) サーバー B は、要求を処理できる場合、サーバー A に応答します。

4) サーバー B が要求を処理できる場合、サーバー A は応答をスタンドアロン クライアントに返します。

5) サーバー B が要求を処理できない場合、サーバー A はサーバー C、サーバー D、サーバー E などに接続しようとします。

これは機能します...独自のスタンドアロンクライアントを持つサーバーBまで、サーバーAがサーバーBに接続しようとすると同時にサーバーAに接続しようとします。衝突が発生し、両方のサーバーが別のサーバーからの応答を無期限に待機します。期限タイマーを使用すると、無期限の待機を回避できますが、問題は解決しません。

これを行う適切な方法は何ですか?

編集: サーバーを別々のスレッドで実行される 2 つのクラス (サーバーと PeerProxy) に分割しましたが、それでもデッドロックが発生します。

これが私がしたことです。Unix リスナーと TCP リスナーを Server クラスと PeerProxy クラスに分割しました。サーバーには独自の io_service があり、PeerProxy にも独自の io_service があります。サーバーが起動すると、2 番目のスレッドで実行されている PeerProxy も起動します (サーバーの実行をブロックしません)。現在、データの流れは次のようになっています。

スタンドアロン クライアント -> サーバー A (応答できない) -> PeerProxy B -> サーバー B (応答がある) -> PeerProxy B -> サーバー A -> スタンドアロン クライアント

サーバー A が PeerProxy B に接続すると同時に、サーバー B のスタンドアロン クライアントが PeerProxy A に接続すると、デッドロックが発生します。

4

2 に答える 2

2

サーバーで各リクエストを非同期に処理する必要があります。つまり、処理を個別の実行スレッドに分割します。こうすることで、サーバーは応答性を維持できます。つまり、サーバーは他のクライアントやサーバーと通信中に、新しい要求に反応できます。

したがって、あなたの場合、2 つのクライアント 1 と 2 がサーバー A と B に要求を送信し、他のサーバーだけが応答できる (またはできない) 場合、2 つのサーバーは次のようになります。

Server A:                                   Server B:
Thread 0  | Thread 1     | Thread 2         Thread 0  | Thread 1     | Thread 2

listen...                                   listen...
-> req 1                                    -> req 2
listen... | handle req 1                    listen... | handle req 2
listen... | forward to B                    listen... | forward to A
-> req B  | wait...                         -> req A  | wait...
listen... | wait...        | handle req B   listen... | wait...      | reject req A  
listen... | -> B: rejected | answer req B   listen... | wait...       
listen... | forward to C                    listen... | -> A: answer       
listen... | -> C: answer                    listen... | req 2 done       
listen... | req 1 done                      listen...
listen...                                   listen...

ここで、各サーバーのスレッド 0 には、着信要求をリッスンし、それらの要求を処理する他のスレッドをスピンする以外の目的はありません。他のスレッドはそれぞれ、応答するか、すべてのサーバーに転送するか、「汚染されている」場合は拒否することにより、1 つの要求を正確に処理します。

注:これらのスレッドは、必ずしも実際のスレッド オブジェクトである必要はありません。これらは、TBB などのスレッド フレームワークの ASIO async* 呼び出しまたは軽量タスクのシーケンスである可能性があります。

更新: Boost.Asio を使用してサーバーを実装する方法について、いくつかの sceleton 疑似コードを投稿します。そのために、Asio の実行を理解するのに役立つと思われる概念を少し紹介したいと思います。async_*呼び出しが状態遷移であり、ハンドラーが状態であるステート マシンのように見ることができます。通常async_*、ハンドラーの実行ごとに 1 つの -call があります。これは、ある状態から別の状態に移行することを意味します。ハンドラーに複数の後続のasync_*-call がある場合、それはハンドラーが実行のセカンダリ スレッドを生成していることを意味します。ハンドラーが関数を呼び出さない場合async_*、対応する実行スレッドが終了します。

では実装へ。

スレッド 0 は、典型的な Asio チュートリアルが示すように、ソケットを作成し、着信接続をリッスンします。唯一のことは、新しいクライアント接続ごとに、新しい実行スレッド (読み取り: 一連のハンドラー) が生成されることです。

accept_handler(client connection) {
  async_read(client_request, request_handler) //spawn new thread of execution
  async_accept(next client connection, accept_handler) //transition to accept_handler
}

スレッド N: は : で始まりrequest_handlerます

request_handler(client_request) {
  if canProcess 
    async_send_answer(client, done_handler) //transition to done_handler
  else  //relay request to first server on list
    async_connect(first server on list, connect_handler) //transition to connect_handler
}  

done_handler通常、成功した応答をログに記録し、別の関数を呼び出しませんasync_*。つまり、クライアントへの接続が閉じられ、実行のスレッドが終了します。

要求を他のサーバーに送信するためのハンドラーのシーケンスは、典型的な接続 - 送信 - 受信 - 切断のシーケンスです。

connect_handler          -- async_send(request) ---------> send_handler
send_handler             -- async_read(answer) ----------> read_handler
read_handler (no answer) -- async_connect(next server) --> connect_handler

いずれかのサーバーから応答を受信した場合、またはリストにサーバーがもう存在しない場合、そのループは終了します。

read_handler (answer ok)       -- async_send_answer(client) --> done_handler
read_handler (no more servers) -- async_send_fail(client) ----> done_handler
于 2013-06-18T08:54:22.840 に答える
0

これは単純な競合状態です。ある種のアトミック ロック変数、セマフォ、またはフラグを実装する必要があるため、あるサーバーが別のサーバーにリクエストを送信しようとすると、その瞬間から他のサーバーからの受信リクエストを拒否します。これを実装するには、おそらく std::atomic を使用します。

于 2013-06-18T08:35:25.630 に答える