停止 TcpListener 的正确方法

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/365370/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 00:40:56  来源:igfitidea点击:

Proper way to stop TcpListener

c#socketsnetworkingtcplistener

提问by Christian P.

I am currently using TcpListener to address incoming connections, each of which are given a thread for handling the communication and then shutdown that single connection. Code looks as follows:

我目前正在使用 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();
}

The listenvariable is a boolean that is a field on the class. Now, when the program shuts down I want it to stop listening for clients. Setting listen to falsewill prevent it from taking on more connections, but since AcceptTcpClientis a blocking call, it will at minimum take the next client and THEN exit. Is there any way to force it to simply break out and stop, right then and there? What effect does calling listener.Stop() have while the other blocking call is running?

listen变量是一个布尔值,是在类的字段。现在,当程序关闭时,我希望它停止监听客户端。设置监听false将阻止它接受更多的连接,但由于AcceptTcpClient是一个阻塞调用,它至少会占用下一个客户端然后退出。有什么方法可以强迫它在当时和那里简单地爆发和停止吗?当另一个阻塞调用正在运行时,调用 listener.Stop() 有什么影响?

采纳答案by Peter Oehlert

There are 2 suggestions I'd make given the code and what I presume is your design. However I'd like to point out first that you should really use non-blocking I/O callbacks when working with I/O like network or filesystems. It's far FARmore efficient and your application will work a lot better though they are harder to program. I'll briefly cover a suggested design modification at the end.

鉴于代码,我会提出 2 条建议,我认为是您的设计。但是,我想首先指出,在处理网络或文件系统等 I/O 时,您确实应该使用非阻塞 I/O 回调。这是迄今为止FAR更高效,应用程序将工作好了很多,虽然他们是很难程序。最后,我将简要介绍建议的设计修改。

  1. Use Using(){} for TcpClient
  2. Thread.Abort()
  3. TcpListener.Pending()
  4. Asynchronous rewrite
  1. 对 TcpClient 使用 Using(){}
  2. 线程中止()
  3. TcpListener.Pending()
  4. 异步重写

Use Using(){} for TcpClient

对 TcpClient 使用 Using(){}

*** Note that you should really enclose your TcpClient call in a using(){} block to ensure that TcpClient.Dispose() or TcpClient.Close() methods are called even in the event of an exception. Alternately you can put this in the finally block of a try {} finally {} block.

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

Thread.Abort()

线程中止()

There are 2 things I see you could do. 1 is that if you have started this TcpListener thread from another you can simply call Thread.Abort instance method on the thread which will cause a threadabortexception to be thrown within the blocking call and walk up the stack.

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

TcpListener.Pending()

TcpListener.Pending()

The second low cost fix would be to use the listener.Pending() method to implement a polling model. You would then use a Thread.Sleep to "wait" before seeing if a new connection is pending. Once you have a pending connection you'd call AcceptTcpClient and that would release the pending connection. The code would look something like this.

第二个低成本解决方案是使用 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();
}

Asynchronous Rewrite

异步重写

Finally I would recommend that you really move to a non-blocking methodology for your application. Under the covers the framework will use Overlapped I/O and I/O completion ports to implement non-blocking I/O from your asynchronous calls. It's not terribly difficult either, it just requires thinking about your code a little differently.

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

Basically you would start your code with the BeginAcceptTcpClient method and keep track of the IAsyncResult that you are returned. You point that at a method whose responsible for getting the TcpClient and passing it off NOTto a new thread but to a thread off of the ThreadPool.QueueUserWorkerItem so you're not spinning up and closing a new thread for each client request (Note you may need to use your own thread pool if you have particularly long lived requests because the thread pool is shared and if you monopolize all the threads other parts of your application implemented by the system may be starved). Once the listener method has kicked off your new TcpClient to it's own ThreadPool request it calls BeginAcceptTcpClient again and points the delegate back at itself.

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

Effectively you're just breaking up your current method into 3 different methods that will then get called by the various parts. 1. to bootstrap everything, 2. to be the target to call EndAcceptTcpClient, kick off the TcpClient to it's own thread and then call itself again, 3. to process the client request and close it when finished.

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

回答by Paul

Probably best to use the asynchronous BeginAcceptTcpClientfunction. Then you can just call Stop() on the listener as it won't be blocking.

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

回答by Mike Scott

Don't use a loop. Instead, call BeginAcceptTcpClient() without a loop. In the callback, just issue another call to BeginAcceptTcpClient(), if your listen flag is still set.

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

To stop the listener, since you've not blocked, your code can just call Close() on it.

要停止侦听器,因为您还没有阻止,您的代码只需在其上调用 Close() 即可。

回答by Dzmitry Huba

Sockets provide powerful asynchronous capabilities. Take a look at Using an Asynchronous Server Socket

套接字提供强大的异步功能。看看使用异步服务器套接字

Here are couple of notes on the code.

这里有一些关于代码的注释。

Using manually created threads in this case may be an overhead.

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

The code below is subject to race conditions - TcpClient.Close() closes network stream you get through TcpClient.GetStream(). Consider closing client where you can definitely say that it is no longer needed.

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

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

TcpClient.Stop() closes underlying socket. TcpCliet.AcceptTcpClient() uses Socket.Accept() method on underlying socket which will throw SocketException once it is closed. You can call it from a different thread.

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

Anyway I recommend asynchronous sockets.

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

回答by Eric Nicholson

Just to add even more reason to use the asynchronous approach, I'm pretty sure Thread.Abort won't work because the call is blocked in the OS level TCP stack.

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

Also... if you are calling BeginAcceptTCPClient in the callback to listen for every connection but the first, be careful to make sure that the thread that executed the initial BeginAccept doesn't terminate or else the listener will automatically get disposed by the framework. I suppose that's a feature, but in practice it's very annoying. In desktop apps it's not usually a problem, but on the web you might want to use the thread pool since those threads don't ever really terminate.

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

回答by Eric Nicholson

Some changes to make the Peter Oehlert anwer perfect. Because before 500 miliseconds the listener bloking again. To correct this:

使 Peter Oehlert anwer 完美的一些更改。因为在 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();
       }
   }

回答by zproxy

listener.Server.Close()from another thread breaks the blocking call.

listener.Server.Close()从另一个线程中断阻塞调用。

A blocking operation was interrupted by a call to WSACancelBlockingCall

回答by Andriy Vandych

See my answer here https://stackoverflow.com/a/17816763/2548170TcpListener.Pending()is not good solution

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

回答by Peter Suwara

Already mentioned above, use BeginAcceptTcpClient instead, it's much easier to manage asynchronously.

上面已经提到,改用BeginAcceptTcpClient,异步管理更容易。

Here is some sample code :

这是一些示例代码:

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