Остановка TcpListener после вызова BeginAcceptTcpClient
у меня есть этот код...
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.Сервер.Закрывать.)( Однако оба они выполняют мою функцию AcceptClient. Затем это создает исключение, когда я вызываю EndAcceptTcpClient. Каков наилучший способ обойти это? Я мог бы просто поставить флаг, чтобы остановить выполнение AcceptClient после того, как я вызвал stop, но я задаюсь вопросом, не упускаю ли я что-то.
обновление 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
Я изменил его, чтобы имплиментировать ответ от Спенсера Рупорта.
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);
}
}
5 ответов
я только что столкнулся с этой проблемой сам, и я считаю, что ваше текущее решение неполное/неправильное. Нет никакой гарантии атомарности между проверкой на IsBound
и последующий вызов EndAcceptTcpClient()
. Вы все равно можете получить исключение, если слушатель Stop()
' d между этими двумя утверждениями. Вы не сказали, какое исключение вы получаете, но я предполагаю, что это то же самое, что и я,ObjectDisposedException
(жалуясь, что базовый сокет уже удален).
вы должен иметь возможность проверить это, имитируя планирование потока:
- установите точку останова на строке после
IsBound
Регистрация обратный звонок - заморозить поток, который попадает в точку останова (окно потоков - > щелкните правой кнопкой мыши, "заморозить")
- Run / trigger код, который вызывает
TcpListener.Stop()
- перерыв и шаг через
EndAcceptTcpClient()
звонок. Вы должны увидетьObjectDisposedException
.
IMO идеальным решением было бы для Microsoft бросьте другое исключение из EndAcceptTcpClient
в этом случае, например,ListenCanceledException
или что-то в этом роде.
как есть, мы должны сделать вывод о том, что происходит из ObjectDisposedException
. Просто поймайте исключение и ведите себя соответственно. В моем коде я молча ем исключение, так как у меня есть код в другом месте, который выполняет реальную работу выключения (т. е. код, который вызвал TcpListener.Stop()
в первую очередь). Вы уже должны иметь обработку исключений в этой области в любом случае, так как вы можете получить различные SocketExceptions
. Этот просто прикрепляет еще один обработчик catch к этому блоку try.
Я признаю, что мне неудобно с этим подходом, так как в принципе улов может быть ложноположительным, с подлинным "плохим" доступом к объекту. Но, с другой стороны, в 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);
}
нет, вы ничего не упускаете. Можно проверить свойство IsBound объекта Socket. По крайней мере, для TCP-соединений, пока сокет слушает, это будет установлено в true, и после вызова close его значение будет false. Хотя, ваша собственная реализация может работать так же хорошо.
попробуйте этот. он отлично работает для меня, не ловя исключений.
private void OnAccept(IAsyncResult pAsyncResult)
{
TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
if(listener.Server == null)
{
//stop method was called
return;
}
...
}
Я думаю, что все древовидные вещи необходимы и что перезапуск BeginAcceptTcpClient должен быть помещен вне tryctach EndAcceptTcpClient.
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();
}
}
Это простой пример того, как начать прослушивание, как асинхронно обрабатывать запросы и как остановить прослушивание.
полный пример здесь.
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
}