web-dev-qa-db-ja.com

TcpListenerを停止する適切な方法

現在、TcpListenerを使用して、着信接続に対処しています。各接続には、通信を処理するためのスレッドが与えられ、その単一の接続をシャットダウンします。コードは次のようになります。

TcpListener listener = new TcpListener(IPAddress.Any, Port);
System.Console.WriteLine("Server Initialized, listening for incoming connections");
listener.Start();
while (listen)
{
     // Step 0: Client connection
     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

listen変数は、クラスのフィールドであるブール値です。これで、プログラムがシャットダウンしたときに、クライアントのリッスンを停止する必要があります。 listenをfalseに設定すると、それ以上の接続を取得できなくなりますが、AcceptTcpClientはブロッキング呼び出しなので、少なくとも次のクライアントを取得して終了します。その場で、簡単に抜け出して停止させる方法はありますか?他のブロッキングコールの実行中にlistener.Stop()を呼び出すと、どのような影響がありますか?

59
Christian P.

コードを考えて2つの提案がありますが、あなたのデザインだと思います。ただし、ネットワークやファイルシステムなどのI/Oを使用する場合は、非ブロッキングI/Oコールバックを実際に使用する必要があることを最初に指摘します。はるかに遠い[〜#〜] far [〜#〜]より効率的であり、アプリケーションはプログラムするのが難しくなりますが、より良く動作します。最後に、提案された設計変更について簡単に説明します。

  1. TcpClientにUsing(){}を使用する
  2. Thread.Abort()
  3. TcpListener.Pending()
  4. 非同期リライト

TcpClientにUsing(){}を使用する

***例外が発生した場合でもTcpClient.Dispose()またはTcpClient.Close()メソッドが確実に呼び出されるように、TcpClient呼び出しをusing(){}ブロックで実際に囲む必要があることに注意してください。あるいは、これをtry {} finally {}ブロックのfinallyブロックに入れることもできます。

Thread.Abort()

あなたにできると思うことは2つあります。 1は、このTcpListenerスレッドを別のスレッドから開始した場合、そのスレッドでThread.Abortインスタンスメソッドを呼び出すだけで、ブロッキング呼び出し内でthreadabortexceptionがスローされ、スタックを上ることができるということです。

TcpListener.Pending()

2番目の低コストの修正方法は、listener.Pending()メソッドを使用してポーリングモデルを実装することです。次に、Thread.Sleepを使用して、新しい接続が保留中かどうかを確認する前に「待機」します。保留中の接続を取得したら、AcceptTcpClientを呼び出すと、保留中の接続が解放されます。コードは次のようになります。

while (listen){
     // Step 0: Client connection
     if (!listener.Pending())
     {
          Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
          continue; // skip to next iteration of loop
     }

     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

非同期リライト

最後に、アプリケーションの非ブロッキング手法に実際に移行することをお勧めします。カバーの下で、フレームワークはオーバーラップI/OおよびI/O完了ポートを使用して、非同期呼び出しからの非ブロッキングI/Oを実装します。それほど難しくもありません。コードについて少し違った考え方をするだけです。

基本的に、BeginAcceptTcpClientメソッドを使用してコードを開始し、返されるIAsyncResultを追跡します。 TcpClientを取得して渡すことを担当するメソッドを指定します[〜#〜] not [〜#〜]新しいスレッドではなく、ThreadPool.QueueUserWorkerItemのスレッドに'クライアントリクエストごとに新しいスレッドをスピンアップして閉じませんシステムによって実装される飢えさせるかもしれません)。リスナーメソッドは、新しいTcpClientを独自のThreadPoolリクエストにキックオフすると、BeginAcceptTcpClientを再度呼び出し、デリゲートを自分自身に向けます。

事実上、現在のメソッドを3つの異なるメソッドに分割し、さまざまな部分から呼び出されるだけです。 1. to bootstrap everything、2. EndAcceptTcpClientを呼び出すターゲットになり、TcpClientを独自のスレッドにキックオフしてから再度呼び出します。3。クライアントリクエストを処理して終了するとき終わった。

58
Peter Oehlert

別のスレッドからのlistener.Server.Close()は、ブロッキング呼び出しを中断します。

A blocking operation was interrupted by a call to WSACancelBlockingCall
48
zproxy

ソケットは強力な非同期機能を提供します。 非同期サーバーソケットの使用 をご覧ください

コードに関するいくつかの注意事項を次に示します。

この場合、手動で作成されたスレッドを使用するとオーバーヘッドが発生する場合があります。

以下のコードは競合状態の影響を受けます-TcpClient.Close()は、TcpClient.GetStream()で取得したネットワークストリームを閉じます。クライアントが不要になったと断言できるクライアントを閉じることを検討してください。

 clientThread.Start(client.GetStream());
 client.Close();

TcpClient.Stop()は、基礎となるソケットを閉じます。 TcpCliet.AcceptTcpClient()は、基になるソケットでSocket.Accept()メソッドを使用し、閉じられるとSocketExceptionをスローします。別のスレッドから呼び出すことができます。

とにかく、非同期ソケットをお勧めします。

3
Dzmitry Huba

ここで私の答えを参照してください https://stackoverflow.com/a/17816763/254817TcpListener.Pending()は良い解決策ではありません

2
Andriy Vandych

ループを使用しないでください。代わりに、ループなしでBeginAcceptTcpClient()を呼び出します。コールバックで、リッスンフラグがまだ設定されている場合は、BeginAcceptTcpClient()をもう一度呼び出します。

リスナーを停止するには、ブロックしていないため、コードでリスナーに対してClose()を呼び出すだけです。

2
Mike Scott

非同期アプローチを使用する理由をさらに追加するために、OSレベルで呼び出しがブロックされているため、Thread.Abortが機能しないことを確信していますTCPスタック。

また...コールバックでBeginAcceptTCPClientを呼び出して、最初の接続を除くすべての接続をリッスンする場合、最初のBeginAcceptを実行したスレッドが終了しないように注意してください。そうしないと、リスナーがフレームワークによって自動的に破棄されますこれは機能だと思いますが、実際には非常に迷惑です。通常、デスクトップアプリでは問題になりませんが、Webではスレッドプールが実際に終了しないため、スレッドプールを使用することをお勧めします。

1
Eric Nicholson

すでに前述したように、代わりにBeginAcceptTcpClientを使用します。非同期で管理する方がはるかに簡単です。

以下にサンプルコードを示します。

        ServerSocket = new TcpListener(endpoint);
        try
        {
            ServerSocket.Start();
            ServerSocket.BeginAcceptTcpClient(OnClientConnect, null);
            ServerStarted = true;

            Console.WriteLine("Server has successfully started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Server was unable to start : {ex.Message}");
            return false;
        }
0
Peter Suwara