私はこのコードを持っています...
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);
}
}
私は自分でこの問題に遭遇しました、そしてあなたの現在の解決策は不完全/間違っていると思います。 IsBound
のチェックとそれに続くEndAcceptTcpClient()
の呼び出しの間の原子性の保証はありません。これらの2つのステートメントの間にリスナーがStop()
されている場合でも、例外が発生する可能性があります。発生している例外については言及していませんが、発生している例外と同じObjectDisposedException
(基になるソケットが既に破棄されているとの不満)だと思います。
スレッドのスケジューリングをシミュレートすることで、これを確認できるはずです。
IsBound
チェック後の行にブレークポイントを設定しますTcpListener.Stop()
を呼び出すコードを実行/トリガーしますEndAcceptTcpClient()
呼び出しをステップスルーします。 ObjectDisposedException
が表示されます。IMOの理想的な解決策は、この場合、MicrosoftがEndAcceptTcpClient
とは異なる例外をスローすることです。 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);
}
いいえ、あなたは何も見逃していません。 SocketオブジェクトのIsBoundプロパティを確認できます。少なくともTCP接続の場合、ソケットがリッスンしている間、これはtrueに設定され、closeを呼び出すと、その値はfalseになります。ただし、独自の実装でも同様に機能します。
これを試してください。例外をキャッチすることなく、私にとっては問題なく動作します。
private void OnAccept(IAsyncResult pAsyncResult)
{
TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
if(listener.Server == null)
{
//stop method was called
return;
}
...
}
これは、リスニングを開始する方法、リクエストを非同期で処理する方法、およびリスニングを停止する方法の簡単な例です。
完全な例 ここ 。
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
}
ツリーのすべてが必要であり、BeginAcceptTcpClientの再起動はEndAcceptTcpClientのtryctachの外に配置する必要があると思います。
private void AcceptTcpClientCallback(IAsyncResult ar)
{
var listener = (TcpListener)ar.AsyncState;
//Sometimes the socket is null and somethimes the socket was set
if (listener.Server == null || !listener.Server.IsBound)
return;
TcpClient client = null;
try
{
client = listener.EndAcceptTcpClient(ar);
}
catch (SocketException ex)
{
//the client is corrupt
OnError(ex);
}
catch (ObjectDisposedException)
{
//Listener canceled
return;
}
//Get the next Client
listener.BeginAcceptTcpClient(new AsyncCallback(AcceptTcpClientCallback), listener);
if (client == null)
return; //Abort if there was an error with the client
MyConnection connection = null;
try
{
//Client-Protocoll init
connection = Connect(client.GetStream());
}
catch (Exception ex)
{
//The client is corrupt/invalid
OnError(ex);
client.Close();
}
}