调用 BeginAcceptTcpClient 后停止 TcpListener
Posted
技术标签:
【中文标题】调用 BeginAcceptTcpClient 后停止 TcpListener【英文标题】:Stopping a TcpListener after calling BeginAcceptTcpClient 【发布时间】:2010-11-13 12:17:35 【问题描述】:我有这个代码...
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
我更改了它以暗示 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);
【问题讨论】:
你不必排队另一个线程来做处理工作;您可以只调用 EndAcceptTcpClient 来告诉侦听器它已被处理,然后在它之后调用 BeginAcceptTcpClient 来安排另一个处理。使用当前线程处理刚刚收到的请求。 【参考方案1】:不,你没有错过任何东西。您可以检查 Socket 对象的 IsBound 属性。至少对于 TCP 连接,当套接字正在侦听时,这将设置为 true,并且在您调用 close 后,它的值将是 false。不过,您自己的实现也可以正常工作。
【讨论】:
太棒了,这正是我想要的!谢谢! 这是的答案;只需检查异步回调中的listener.Server.IsBound
,如果为假,则返回。无需调用 EndAccept*
然后捕获(预期和记录的)异常。【参考方案2】:
我自己也遇到了这个问题,我认为您当前的解决方案不完整/不正确。检查IsBound
和随后调用EndAcceptTcpClient()
之间不能保证原子性。如果侦听器在这两个语句之间是Stop()
'd,您仍然可以获得异常。你没有说你得到了什么异常,但我认为它与我得到的相同,ObjectDisposedException
(抱怨底层套接字已经被释放)。
您应该可以通过模拟线程调度来检查这一点:
在IsBound
签入回调后的行上设置断点
冻结命中断点的线程(线程窗口 -> 右键单击,“冻结”)
运行/触发调用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);
【讨论】:
是的,我最终设置了一个停止布尔值,在我关闭之前设置。然后我在调用 end accept 之前检查一下。 即使这种方法也存在线程问题。基本上,除非有一个锁同时涵盖布尔值和对 TcpListener 的调用,否则您可以获得导致异常的线程调度:1)回调检查布尔值,我们仍在监听,很酷,2)线程被换出设置布尔值并调用 Stop() 的线程,3) 回调线程恢复并调用 EndAccept。 为什么你会通过在一个关闭的监听器上调用EndAcceptTcpClient
来引起ObjectDisposedException
,而不是首先检查它是否仍然存在,然后简单地从回调中返回?【参考方案3】:
试试这个。它对我来说很好,不会捕获异常。
private void OnAccept(IAsyncResult pAsyncResult)
TcpListener listener = (TcpListener) pAsyncResult.AsyncState;
if(listener.Server == null)
//stop method was called
return;
...
【讨论】:
这个问题是 Stop() 实际上为服务器重新创建了一个新的(未绑定的)套接字。 Start() 然后绑定这个套接字(到它创建的 IPEndPoint 或 IPAddress + 端口)并开始它的侦听。这就是 IsBound 更准确的原因。【参考方案4】:我认为所有树的东西都是需要的,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();
【讨论】:
【参考方案5】:这是一个如何开始监听、如何异步处理请求以及如何停止监听的简单示例。
完整示例here。
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 后停止 TcpListener的主要内容,如果未能解决你的问题,请参考以下文章