停止 TcpListener 的正确方法

Posted

技术标签:

【中文标题】停止 TcpListener 的正确方法【英文标题】:Proper way to stop TcpListener 【发布时间】:2010-09-26 19:03:15 【问题描述】:

我目前正在使用 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() 有什么影响?

【问题讨论】:

【参考方案1】:

鉴于代码和我认为是您的设计,这是您可以使用的两个快速修复:

1。线程.Abort()

如果您从另一个线程启动了这个 TcpListener 线程,您可以简单地在该线程上调用 Abort(),这将在阻塞调用中产生一个 ThreadAbortException 并沿堆栈向上移动。

2。 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 并将其传递给新线程但传递给ThreadPool.QueueUserWorkerItem 的线程的方法,因此您不会启动和关闭每个客户端请求都有一个新线程(注意:如果您有特别长的请求,您可能需要使用自己的线程池,因为线程池是共享的,如果您垄断了所有线程,系统实现的应用程序的其他部分可能饿死)。一旦侦听器方法将您的新TcpClient 启动到它自己的ThreadPool 请求,它就会再次调用BeginAcceptTcpClient() 并将代理指向它自己。

实际上,您只是将当前方法分解为 3 个不同的方法,然后由各个部分调用:

    引导一切; 成为调用EndAcceptTcpClient()的目标,将TcpClient启动到自己的线程,然后再次调用自己; 处理客户端请求并在完成后关闭它。

(注意:您应该将TcpClient 调用包含在using() 块中,以确保即使在发生异常时也调用TcpClient.Dispose()TcpClient.Close() 方法。或者您可以将它放在try finally 块的finally 块中。)

【讨论】:

我刚刚阅读了您的答案,但对我来说,不清楚如何正确实施您所描述的内容。你能提供一些代码来澄清它。谢谢 在我的测试中,当线程被阻塞等待连接时,Thread.Abort 似乎无法按预期工作,请在此处查看我的问题:***.com/questions/5778267/… 您确定可以在 clientThread 使用 client.GetStream() 时关闭客户端吗?如果我离开 client.Close() 调用,我无法在 HandleConnection 方法中的 NetworkStream 上创建 StreamReader。 为什么这被标记为解决方案?由于某种原因,前 3 点似乎是错误的,最后一个可能使用一些代码。 TcpClient 没有实现IDisposable。这个答案有几个不好的地方,不应该被接受。【参考方案2】:

来自另一个线程的listener.Server.Close() 中断了阻塞调用。

A blocking operation was interrupted by a call to WSACancelBlockingCall

【讨论】:

虽然我喜欢 Peter Oehlert 对完整性和最佳实践的回答,但我有一个简单的任务并不能证明完全异步重写的复杂性,而且我发现 Thread.Abort() 不起作用为了我。然而,这对我来说非常快速有效。 我更喜欢这个答案,好像你要重写它,你可能应该使用 WCF,它解决了我在 .net 中使用 TCP/IP 套接字时遇到的许多问题 你甚至可以添加一些行。在您的 SocketException 捕获处理程序中。 if ((e.SocketErrorCode == SocketError.Interrupted)) Console.WriteLine("A blocking listen has been cancelled"); 所以你确定你在等待客户端连接时终止了。 为什么不做 listener.Stop(); 我同意@John;为什么叫listener.Server.Close() 而不是listener.Stop()?如果您查看Reference Source for TcpListener.Stop(),会为您提供相当于listener.Server.Close() 的功能(以及清除旧连接请求等)。【参考方案3】:

不要使用循环。相反,在没有循环的情况下调用 BeginAcceptTcpClient()。在回调中,如果您的监听标志仍然设置,只需再次调用 BeginAcceptTcpClient()。

要停止侦听器,由于您没有被阻止,您的代码只需调用 Close() 即可。

【讨论】:

你能举个例子吗? @omJohn8372 我写的东西没有什么要补充的了。您的回调方法检查的某处有一个标志。您在调用 BeginAcceptTcpClient 时提供该回调。有关回调的示例,请参阅Microsoft docs。因此,当您想停止侦听器时,请在其上设置标志和 Close()。在回调中,你首先检查标志是否被设置,如果是,你知道它已经被关闭并退出。【参考方案4】:

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

这里有几个关于代码的注释。

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

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

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

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

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

【讨论】:

文档特别指出关闭 TcpClient 不会关闭底层流。 是的,文档是这么说的,但是实现会关闭流...检查它 TcpClient tcpClient = new TcpClient(); tcpClient.Connect("www.google.com", 80); NetworkStream networkStream = tcpClient.GetStream(); tcpClient.Close();字节[]字节=新字节[1024]; networkStream.Read(bytes, 0, 1024); 没有TcpClient.Stop()方法。【参考方案5】:

在这里查看我的答案https://***.com/a/17816763/2548170 TcpListener.Pending() 不是很好的解决方案

【讨论】:

将问题标记为重复或提供完整答案。【参考方案6】:

只是为了增加使用异步方法的更多理由,我很确定 Thread.Abort 不会工作,因为调用在操作系统级别的 TCP 堆栈中被阻止。

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

【讨论】:

【参考方案7】:

上面已经提到了,改用 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;
        

【讨论】:

【参考方案8】:

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

【讨论】:

【参考方案9】:

一些改变使 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();
       
   

【讨论】:

Elimar,我在这段代码和我自己的代码之间看到的唯一变化是 else 块。因为 continue 基本上会跳到 while 循环的顶部,所以 else 并不是绝对必要的。可以根据清洁度或编码风格的优点进行争论,但不是必需的。我还缺少其他东西吗?

以上是关于停止 TcpListener 的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

调用 BeginAcceptTcpClient 后停止 TcpListener

关闭 TcpListener 和 TcpClient 连接的正确顺序(哪一侧应该是主动关闭)

程序退出后,TcpListener Socket 仍然处于活动状态

TcpListener的BeginAcceptTcpClient方法的两个参数是啥意思

TcpClient类与TcpListener类

c#_TcpListener&TcpClient