正确的方法来停止TcpListener

时间:2008-12-13 16:12:27

标签: c# sockets networking tcplistener

我目前正在使用TcpListener来处理传入连接,每个连接都有一个用于处理通信的线程,然后关闭该单个连接。代码如下:

TcpListener listener = new TcpListener(IPAddress.Any, Port);
System.Console.WriteLine("Server Initialized, listening for incoming connections");
listener.Start();
while (listen)
{
     // Step 0: Client connection
     TcpClient client = listener.AcceptTcpClient();
     Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
     clientThread.Start(client.GetStream());
     client.Close();
}

listen变量是一个布尔值,它是类中的一个字段。现在,当程序关闭时,我希望它停止监听客户端。设置监听false会阻止它接收更多连接,但由于AcceptTcpClient是阻塞呼叫,它至少会占用下一个客户端,然后退出。有没有办法迫使它简单地突破并停止,就在那时?当另一个阻塞调用正在运行时,调用listener.Stop()会产生什么影响?

9 个答案:

答案 0 :(得分:57)

鉴于代码我有两个建议,我认为是你的设计。但是,我想首先指出在使用I / O(如网络或文件系统)时应该使用非阻塞I / O回调。它远远超过 FAR ,并且您的应用程序将更好地工作,尽管它们更难编程。我将在最后简要介绍一下建议的设计修改。

  1. 使用(){}用于TcpClient
  2. Thread.Abort的()
  3. TcpListener.Pending()
  4. 异步重写
  5. 使用(){}用于TcpClient

    ***请注意,您应该将您的TcpClient调用封装在using(){}块中,以确保即使在发生异常时也会调用TcpClient.Dispose()或TcpClient.Close()方法。或者你可以将它放在try {} finally {}块的finally块中。

    Thread.Abort的()

    我认为你可以做两件事。 1如果你已经从另一个线程启动了这个TcpListener线程,你可以简单地在线程上调用Thread.Abort实例方法,这将导致在阻塞调用中抛出threadabortexception并向上移动堆栈。

    TcpListener.Pending()

    第二个低成本修复方法是使用listener.Pending()方法来实现轮询模型。然后,您将在查看新连接是否挂起之前使用Thread.Sleep“等待”。一旦你有一个挂起的连接,你就会调用AcceptTcpClient,这将释放挂起的连接。代码看起来像这样。

    while (listen){
         // Step 0: Client connection
         if (!listener.Pending())
         {
              Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
              continue; // skip to next iteration of loop
         }
    
         TcpClient client = listener.AcceptTcpClient();
         Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
         clientThread.Start(client.GetStream());
         client.Close();
    }
    

    异步重写

    最后,我建议您真正转向应用程序的非阻塞方法。在框架下,框架将使用重叠I / O和I / O完成端口来实现异步调用的非阻塞I / O.它也不是非常困难,只需要稍微考虑一下你的代码。

    基本上,您可以使用BeginAcceptTcpClient方法启动代码,并跟踪返回的IAsyncResult。你指的是一个方法,它负责获取TcpClient并将它从 NOT 传递给新线程,但是传递给ThreadPool.QueueUserWorkerItem的一个线程,这样你就不会旋转并关闭一个新线程对于每个客户端请求(注意,如果您有特别长时间的请求,则可能需要使用自己的线程池,因为线程池是共享的,如果您独占所有线程,则系统实现的应用程序的其他部分可能会被饿死)。一旦监听器方法将新的TcpClient启动到它自己的ThreadPool请求,它再次调用BeginAcceptTcpClient并将代理指回自身。

    实际上,您只是将当前的方法分解为3种不同的方法,然后由各个部分调用。 1.引导所有内容,2。成为调用EndAcceptTcpClient的目标,启动TcpClient到它自己的线程然后再调用自己,3。处理客户端请求并在完成时关闭它。

答案 1 :(得分:47)

来自另一个线程的

listener.Server.Close()打破了阻塞调用。

A blocking operation was interrupted by a call to WSACancelBlockingCall

答案 2 :(得分:3)

套接字提供强大的异步功能。看看Using an Asynchronous Server Socket

以下是关于代码的几点说明。

在这种情况下使用手动创建的线程可能是一种开销。

以下代码受竞争条件限制 - TcpClient.Close()关闭您通过TcpClient.GetStream()获得的网络流。考虑关闭客户端,您可以肯定地说不再需要它。

 clientThread.Start(client.GetStream());
 client.Close();

TcpClient.Stop()关闭底层套接字。 TcpCliet.AcceptTcpClient()在底层套接字上使用Socket.Accept()方法,一旦它关闭就会抛出SocketException。你可以从另一个线程调用它。

无论如何我推荐使用异步套接字。

答案 3 :(得分:2)

不要使用循环。而是在没有循环的情况下调用BeginAcceptTcpClient()。在回调中,如果仍然设置了listen标志,则只发出对BeginAcceptTcpClient()的另一个调用。

要停止监听器,因为您没有阻止,您的代码只能在其上调用Close()。

答案 4 :(得分:2)

在此处查看我的回答https://stackoverflow.com/a/17816763/2548170 TcpListener.Pending()不是好解决方案

答案 5 :(得分:1)

只是为了添加使用异步方法的更多理由,我非常确定Thread.Abort不起作用,因为调用在OS级别的TCP堆栈中被阻止。

另外......如果你在回调中调用BeginAcceptTCPClient来监听除第一个连接之外的所有连接,请注意确保执行初始BeginAccept的线程不会终止,否则侦听器将自动被处理掉框架。我想这是一个功能,但在实践中它非常烦人。在桌面应用程序中,它通常不是问题,但在Web上,您可能希望使用线程池,因为这些线程永远不会真正终止。

答案 6 :(得分:0)

如上所述,请改用BeginAcceptTcpClient,异步管理要容易得多。

以下是一些示例代码:

        ServerSocket = new TcpListener(endpoint);
        try
        {
            ServerSocket.Start();
            ServerSocket.BeginAcceptTcpClient(OnClientConnect, null);
            ServerStarted = true;

            Console.WriteLine("Server has successfully started.");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Server was unable to start : {ex.Message}");
            return false;
        }

答案 7 :(得分:-1)

可能最好使用异步BeginAcceptTcpClient函数。然后你就可以在监听器上调用Stop(),因为它不会阻塞。

答案 8 :(得分:-2)

一些改变使Peter Oehlert完美无缺。因为在500毫秒之前,监听器再次阻塞。要纠正这个:

    while (listen)     
    {
       // Step 0: Client connection     
       if (!listener.Pending())     
       {
           Thread.Sleep(500); // choose a number (in milliseconds) that makes sense
           continue; // skip to next iteration of loop
       }
       else // Enter here only if have pending clients
       {
          TcpClient client = listener.AcceptTcpClient();
          Thread clientThread = new Thread(new ParameterizedThreadStart(HandleConnection));
          clientThread.Start(client.GetStream());
          client.Close();
       }
   }