Остановка 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.Server.Close(). Однако оба они выполняют мою функцию AcceptClient. Затем это вызывает исключение, когда я вызываю EndAcceptTcpClient. Какова наилучшая практика? Я мог бы просто поставить флаг, чтобы остановить выполнение 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
Я изменил его, чтобы применить ответ от Spencer Ruport.
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);
}
}
Ответы
Ответ 1
Я просто столкнулся с этим вопросом и считаю, что ваше текущее решение является неполным/неправильным. Нет гарантии атомарности между проверкой на IsBound
и последующим вызовом EndAcceptTcpClient()
. Вы можете получить исключение, если слушатель Stop()
'd между этими двумя утверждениями. Вы не сказали, какое исключение вы получаете, но я предполагаю, что тот же самый, который я получаю, ObjectDisposedException
(жалуясь, что базовый сокет уже был удален).
Вы можете проверить это, смоделировав планирование потоков:
- Установите контрольную точку в строке после
IsBound
проверки обратного вызова
- Заморозить поток, который попадает в точку останова (окно Threads → щелчок правой кнопкой мыши, "замораживание" )
- Запустить/запустить код, вызывающий
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);
}
Ответ 2
Нет, вы ничего не пропустили. Вы можете проверить свойство IsBound объекта Socket. По крайней мере для TCP-соединений, в то время как сокет прослушивает, это будет установлено в true, и после того, как вы вызовете его, значение будет ложным. Хотя, ваша собственная реализация может работать так же хорошо.
Ответ 3
попробуйте этот. он отлично работает для меня, не перехватывая исключения.
private void OnAccept(IAsyncResult pAsyncResult)
{
TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
if(listener.Server == null)
{
//stop method was called
return;
}
...
}
Ответ 4
Я думаю, что все вещи дерева необходимы и что перезапуск 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();
}
}
Ответ 5
Это простой пример, как начать прослушивание, как обрабатывать запросы асинхронно и как прекратить прослушивание.
Полный пример здесь.
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
}