【问题标题】:Stopping a TcpListener after calling BeginAcceptTcpClient调用 BeginAcceptTcpClient 后停止 TcpListener
【发布时间】: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 来安排另一个处理。使用当前线程处理刚刚收到的请求。

标签: c# sockets tcpclient


【解决方案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
    }
    

    【讨论】:

      猜你喜欢
      • 2016-12-18
      • 2010-09-26
      • 2015-07-08
      • 1970-01-01
      • 1970-01-01
      • 2013-10-13
      • 2012-05-14
      • 1970-01-01
      • 2015-10-04
      相关资源
      最近更新 更多