c# - 调用 BeginAcceptTcpClient 后停止 TcpListener

标签 c# sockets tcpclient

我有这个代码...

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);
    }
}

最佳答案

我自己刚遇到这个问题,我认为您当前的解决方案不完整/不正确。在检查 IsBound 和随后调用 EndAcceptTcpClient() 之间不保证原子性。如果监听器在这两个语句之间 Stop(),您仍然可以获得异常。您没有说明您得到的是什么异常,但我认为它与我得到的是同一个异常,ObjectDisposedException(提示底层套接字已被处置)。

您应该能够通过模拟线程调度来检查这一点:

  • IsBound 检查回调后的行上设置断点
  • 卡住遇到断点的线程(“线程”窗口 -> 右键单击​​“卡住”)
  • 运行/触发调用TcpListener.Stop()的代码
  • 进入并单步执行 EndAcceptTcpClient() 调用。您应该会看到 ObjectDisposedException

在这种情况下,IMO 的理想解决方案是让 Microsoft 抛出与 EndAcceptTcpClient 不同的异常,例如ListenCanceledException 或类似的东西。

实际上,我们必须从 ObjectDisposedException 推断发生了什么。只需捕获异常并相应地进行操作即可。在我的代码中,我默默地吃掉了异常,因为我在其他地方有代码在做真正的关闭工作(即首先调用 TcpListener.Stop() 的代码)。无论如何,您应该已经在该区域进行了异常处理,因为您可以获得各种 SocketExceptions。这只是将另一个 catch 处理程序附加到该 try block 上。

我承认我对这种方法感到不舒服,因为原则上捕获可能是误报,其中存在真正的“坏”对象访问。但另一方面,EndAcceptTcpClient() 调用中没有太多可能触发此异常的对象访问。我希望。

这是我的代码。这是早期/原型(prototype)的东西,请忽略控制台调用。

    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);
    }

关于c# - 调用 BeginAcceptTcpClient 后停止 TcpListener,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1173774/

相关文章:

c# - 在多个线程中重复使用单个HttpClient时,套接字用完了-仍然有1000个TIMED_WAIT连接

c# - 将单元格中的数据分配给变量时遇到问题

c# - 右侧的 Datagridview 卡住列

java - Java中过滤ASCII流

c - C 中的 TCP/IP 编程

java - 在同一个 TCP 套接字上接收不同类型的对象

从异步方法获取更新时的 C# 封装

c# - MVC 3 中可用的网球场接口(interface) : Errors and Questions

c# - MSI 安装程序自定义操作身份问题

java - Java的DatagramChannel.write()的含义