阻止 TcpListener 的正确方法

我目前正在使用 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变量是一个布尔值,它是类上的一个字段。现在,当程序关闭时,我希望它停止监听客户端。设置 listen to false将阻止它接受更多的连接,但是由于 AcceptTcpClient是一个阻塞调用,它至少会接受下一个客户端和 THEN 退出。有没有办法迫使它突然爆发然后停止,就在那时那地?调用监听器有什么效果。其他阻塞调用正在运行时,Stop () have?

92319 次浏览

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

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

要停止侦听器,因为您没有阻塞,您的代码可以直接对其调用 Close ()。

考虑到代码和我假设的设计,以下是您可以使用的两个快速修复方法:

1. 线程。中止()

如果您已经从另一个线程启动了这个 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个不同的方法,然后这3个方法会被不同的部分调用:

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

(注意: 您应该将 TcpClient调用封装在 using(){}块中,以确保即使在异常情况下也能调用 TcpClient.Dispose()TcpClient.Close()方法。或者你可以把它放在 try {} finally {}块的 finally块中。)

套接字提供了强大的异步能力

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

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

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

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

TcpClient.Stop ()关闭底层套接字。TcpCliet.AcceptTcpClient ()使用 Socket。底层套接字上的 Accept ()方法,该方法关闭后将引发 SocketException。您可以从另一个线程调用它。

无论如何,我建议使用异步套接字。

为了增加使用异步方法的更多理由,我非常确定 Thread。中止无法工作,因为调用在 OS 级 TCP 堆栈中被阻塞。

另外... ... 如果您在回调中调用 BeginAcceptTCPClient 来监听除第一个连接之外的每个连接,请注意确保执行初始 BeginAccept 的线程不会终止,否则监听器将被框架自动释放。我想这是一个特点,但实际上它非常烦人。在桌面应用程序中,这通常不是问题,但是在网络上,你可能需要使用线程池,因为这些线程从来没有真正终止过。

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

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

A blocking operation was interrupted by a call to WSACancelBlockingCall

点击这里查看我的答案 < a href = “ https://stackoverflow./a/17816763/2548170”> https://stackoverflow.com/a/17816763/2548170 TcpListener.Pending()不是一个好的解决方案

上面已经提到过,使用 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;
}