22

私はこのコードを持っています...

internal static void Start()
{
    TcpListener listenerSocket = new TcpListener(IPAddress.Any, 32599);
    listenerSocket.Start();
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}

次に、私のコールバック関数は次のようになります...

private static void AcceptClient(IAsyncResult asyncResult)
{
    MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
    ThreadPool.QueueUserWorkItem((object state) => handler.Process());
    listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
}

ここで、BeginAcceptTcpClientを呼び出し、しばらくしてからサーバーを停止します。これを行うために、私はTcpListener.Stop()またはTcpListener.Server.Close()を呼び出しています。ただし、これらは両方とも私のAcceptClient関数を実行します。EndAcceptTcpClientを呼び出すと、これにより例外がスローされます。これを回避するためのベストプラクティスの方法は何ですか?stopを呼び出したら、AcceptClientの実行を停止するフラグを設定することもできますが、何かが足りないのではないかと思います。

アップデート1

現在、コードを次のように変更してパッチを適用しています。

private static void AcceptClient(IAsyncResult asyncResult)
{
     if (!shutdown)
     {
          MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
          ThreadPool.QueueUserWorkItem((object state) => handler.Process());
          listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
     }
}

private static bool shutdown = false;
internal static void Stop()
{
     shutdown = true;
     listenerSocket.Stop();
}

アップデート2

SpencerRuportからの回答を暗示するように変更しました。

private static void AcceptClient(IAsyncResult asyncResult)
{
    if (listenerSocket.Server.IsBound)
    {
            MessageHandler handler = new MessageHandler(listenerSocket.EndAcceptTcpClient(asyncResult));
            ThreadPool.QueueUserWorkItem((object state) => handler.Process());
            listenerSocket.BeginAcceptTcpClient(new AsyncCallback(AcceptClient), null);
    }
}
4

5 に答える 5

17

私は自分でこの問題に遭遇しました.現在の解決策は不完全/間違っていると思います. のチェックIsBoundとそれに続く の呼び出しの間の原子性は保証されませんEndAcceptTcpClient()Stop()これらの 2 つのステートメントの間にリスナーが含まれている場合でも、例外が発生する可能性があります。どの例外が発生しているのかはわかりませんでしたが、それは私が発生しているものと同じだと思いますObjectDisposedException(基になるソケットが既に破棄されていると不平を言っています)。

スレッドのスケジューリングをシミュレートすることで、これを確認できるはずです。

  • IsBoundコールバックのチェック後に行にブレークポイントを設定します
  • ブレークポイントにヒットしたスレッドをフリーズします ([スレッド] ウィンドウ -> 右クリック、[フリーズ])
  • 呼び出すコードを実行/トリガーするTcpListener.Stop()
  • 割り込んで、通話をステップスルーしEndAcceptTcpClient()ます。が表示されますObjectDisposedException

EndAcceptTcpClientIMOの理想的な解決策は、Microsoftがこの場合とは異なる例外をスローすることです。たとえばListenCanceledException、そのようなものです。

そのままでは、 から何が起こっているかを推測する必要がありObjectDisposedExceptionます。例外をキャッチして、それに応じて動作するだけです。私のコードでは、実際のシャットダウン作業を行っているコード (つまりTcpListener.Stop()、最初に呼び出したコード) が別の場所にあるため、黙って例外を食べます。さまざまなSocketExceptions. これは、その try ブロックに別の catch ハンドラーを追加しているだけです。

原則として、キャッチは偽陽性であり、本物の「悪い」オブジェクトアクセスが含まれている可能性があるため、このアプローチには不快感を覚えます。しかし一方で、EndAcceptTcpClient()この例外をトリガーする可能性のある呼び出しでのオブジェクト アクセスはあまり多くありません。私は願います。

これが私のコードです。これは初期/プロトタイプのものです。コンソール呼び出しは無視してください。

    private void OnAccept(IAsyncResult iar)
    {
        TcpListener l = (TcpListener) iar.AsyncState;
        TcpClient c;
        try
        {
            c = l.EndAcceptTcpClient(iar);
            // keep listening
            l.BeginAcceptTcpClient(new AsyncCallback(OnAccept), l);
        }
        catch (SocketException ex)
        {
            Console.WriteLine("Error accepting TCP connection: {0}", ex.Message);

            // unrecoverable
            _doneEvent.Set();
            return;
        }
        catch (ObjectDisposedException)
        {
            // The listener was Stop()'d, disposing the underlying socket and
            // triggering the completion of the callback. We're already exiting,
            // so just return.
            Console.WriteLine("Listen canceled.");
            return;
        }

        // meanwhile...
        SslStream s = new SslStream(c.GetStream());
        Console.WriteLine("Authenticating...");
        s.BeginAuthenticateAsServer(_cert, new AsyncCallback(OnAuthenticate), s);
    }
于 2009-08-04T22:40:53.190 に答える
8

いいえ、何も見逃していません。Socket オブジェクトの IsBound プロパティを確認できます。少なくとも TCP 接続の場合、ソケットがリッスンしている間は true に設定され、close を呼び出した後は値が false になります。ただし、独自の実装も同様に機能します。

于 2009-07-23T19:39:21.553 に答える
1

これを試してください。例外をキャッチすることなく、私にとってはうまく機能します。

private void OnAccept(IAsyncResult pAsyncResult)
{
    TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
    if(listener.Server == null)
    {
        //stop method was called
        return;
    }
    ...
}
于 2010-11-18T12:56:54.767 に答える
0

これは、リッスンを開始する方法、要求を非同期的に処理する方法、およびリッスンを停止する方法の簡単な例です。

ここに完全な例があります。

public class TcpServer
{
    #region Public.     
    // Create new instance of TcpServer.
    public TcpServer(string ip, int port)
    {
        _listener = new TcpListener(IPAddress.Parse(ip), port);
    }

    // Starts receiving incoming requests.      
    public void Start()
    {
        _listener.Start();
        _ct = _cts.Token;
        _listener.BeginAcceptTcpClient(ProcessRequest, _listener);
    }

    // Stops receiving incoming requests.
    public void Stop()
    { 
        // If listening has been cancelled, simply go out from method.
        if(_ct.IsCancellationRequested)
        {
            return;
        }

        // Cancels listening.
        _cts.Cancel();

        // Waits a little, to guarantee 
        // that all operation receive information about cancellation.
        Thread.Sleep(100);
        _listener.Stop();
    }
    #endregion

    #region Private.
    // Process single request.
    private void ProcessRequest(IAsyncResult ar)
    { 
        //Stop if operation was cancelled.
        if(_ct.IsCancellationRequested)
        {
            return;
        }

        var listener = ar.AsyncState as TcpListener;
        if(listener == null)
        {
            return;
        }

        // Check cancellation again. Stop if operation was cancelled.
        if(_ct.IsCancellationRequested)
        {
            return;
        }

        // Starts waiting for the next request.
        listener.BeginAcceptTcpClient(ProcessRequest, listener);

        // Gets client and starts processing received request.
        using(TcpClient client = listener.EndAcceptTcpClient(ar))
        {
            var rp = new RequestProcessor();
            rp.Proccess(client);
        }
    }
    #endregion

    #region Fields.
    private CancellationToken _ct;
    private CancellationTokenSource _cts = new CancellationTokenSource();
    private TcpListener _listener;
    #endregion
}
于 2015-10-01T14:52:52.267 に答える