正确的方法来停止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(); }
listen
variables是一个布尔types,它是类中的一个字段。 现在,当程序closures时,我希望它停止监听客户端。 将listen设置为false
将防止它接受更多的连接,但是由于AcceptTcpClient
是一个阻塞调用,它将至less取下一个客户端,然后退出。 有什么办法可以迫使它只是突然停止,那么在那里? 调用listener.Stop()时有什么效果,而另一个阻塞调用正在运行?
有两个build议,我给了代码,我认为是你的devise。 不过,我想首先指出,在使用I / O(如networking或文件系统)时,应该真正使用非阻塞I / Ocallback。 远远更高的效率,你的应用程序会更好地工作,虽然他们很难编程。 我将在最后简要介绍一下build议的devise修改。
- 对TcpClient使用Using(){}
- Thread.Abort的()
- TcpListener.Pending()
- 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(); } }