我目前正在使用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()会产生什么影响?
答案 0 :(得分:57)
鉴于代码我有两个建议,我认为是你的设计。但是,我想首先指出在使用I / O(如网络或文件系统)时应该使用非阻塞I / O回调。它远远超过 FAR ,并且您的应用程序将更好地工作,尽管它们更难编程。我将在最后简要介绍一下建议的设计修改。
***请注意,您应该将您的TcpClient调用封装在using(){}块中,以确保即使在发生异常时也会调用TcpClient.Dispose()或TcpClient.Close()方法。或者你可以将它放在try {} finally {}块的finally块中。
我认为你可以做两件事。 1如果你已经从另一个线程启动了这个TcpListener线程,你可以简单地在线程上调用Thread.Abort实例方法,这将导致在阻塞调用中抛出threadabortexception并向上移动堆栈。
第二个低成本修复方法是使用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();
}
}