正确的方法来停止TcpListener

我目前使用TcpListener来解决传入的连接,每个连接都有一个处理通信的线程,然后closures那个单一的连接。 代码如下所示:

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

listenvariables是一个布尔types,它是类中的一个字段。 现在,当程序closures时,我希望它停止监听客户端。 将listen设置为false将防止它接受更多的连接,但是由于AcceptTcpClient是一个阻塞调用,它将至less取下一个客户端,然后退出。 有什么办法可以迫使它只是突然停止,那么在那里? 调用listener.Stop()时有什么效果,而另一个阻塞调用正在运行?

有两个build议,我给了代码,我认为是你的devise。 不过,我想首先指出,在使用I / O(如networking或文件系统)时,应该真正使用非阻塞I / Ocallback。 远远更高的效率,你的应用程序会更好地工作,虽然他们很难编程。 我将在最后简要介绍一下build议的devise修改。

  1. 对TcpClient使用Using(){}
  2. Thread.Abort的()
  3. TcpListener.Pending()
  4. asynchronous重写

对TcpClient使用Using(){}

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

Thread.Abort的()

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

TcpListener.Pending()

第二个低成本的解决scheme是使用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重写

最后,我会build议你真的转向你的应用程序的非阻塞方法。 在这个框架下,框架将使用重叠的I / O和I / O完成端口来实现asynchronous调用的非阻塞I / O。 这也不是非常困难,它只是需要以不同的方式思考你的代码。

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

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

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

 A blocking operation was interrupted by a call to WSACancelBlockingCall 

套接字提供了强大的asynchronousfunction。 看看使用asynchronous服务器套接字

这里有几个关于代码的笔记。

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

下面的代码受到竞争条件的影响 – TcpClient.Close()closures通过TcpClient.GetStream()获得的networkingstream。 考虑closures客户,你可以肯定地说,它不再需要。

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

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

无论如何,我build议asynchronous套接字。

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

要停止监听器,因为你没有被阻塞,所以你的代码可以在它上面调用Close()。

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

另外…如果您在callback中调用BeginAcceptTCPClient以侦听每个连接,但首先要注意确保执行初始BeginAccept的线程不会终止,否则监听器将自动被框架处置。 我想这是一个function,但在实践中,这是非常烦人的。 在桌面应用程序中,这通常不是一个问题,但是在networking上,您可能需要使用线程池,因为这些线程并不真正终止。

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

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

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