TcpListener:检测客户端断开连接,而不是客户端暂时不发送任何数据

我正在寻找在使用TcpListener时如何检测“客户端断开连接”。

所有答案似乎都与此类似: TcpListener:如何检测客户端断开连接?

基本上,从流中读取,如果Read()返回0,则客户端已断开连接。

但这是假设客户端在发送的每个数据流之后断开连接。 我们在TCP连接/断开开销既缓慢又昂贵的环境中运行。

我们建立连接然后发送一些请求。

伪代码:

client.Connect(); client.GetStatus(); client.DoSomething(); client.DoSomethingElse(); client.AndSoOn(); client.Disconnect(); 

Connect和Disconnect()之间的每次调用都会向服务器发送数据流。 服务器知道如何分析和处理流。

如果让TcpListener在没有断开连接的情况下读取循环,则会读取并处理所有消息,但在客户端断开连接后,服务器无法知道它并且它永远不会释放客户端并接受新客户端。

 var read = client.GetStream().Read(buffer, 0, buffer.Length); if (read > 0) { //Process } 

如果我让TcpListener在读取== 0时删除客户端,它只接受第一个数据流,只是在之后立即丢弃客户端。

当然这意味着新客户可以连接。

呼叫之间没有人为的延迟,但就计算机时间而言,两次呼叫之间的时间当然是“巨大的”,因此总会有一段时间读取== 0,即使这并不意味着客户端已经或应该断开连接。

 var read = client.GetStream().Read(buffer, 0, buffer.Length); if (read > 0) { //Process } else { break; //Always executed as soon as the first stream of data has been received } 

所以我想知道…有没有更好的方法来检测客户端是否已断开连接?

您可以使用NetworkStream.Socket属性获取底层套接字,并使用它的Receive方法进行读取。

NetworkStream.Read不同, Socket.Receive的链接重载将一直阻塞,直到读取了指定的字节数,并且只有在远程主机关闭TCP连接时才会返回零。


更新: @ jrh的注释是正确的, NetworkStream.Socket是受保护的属性,在此上下文中无法访问。 为了获取客户端Socket ,您可以使用TcpListener.AcceptSocket方法,该方法返回与新建立的连接对应的Socket对象。

Eren的回答为我解决了这个问题。 如果其他人面临同样的问题,这里使用Socket.Receive方法的一些“示例”代码:

 private void AcceptClientAndProcess() { try { client = server.Accept(); client.ReceiveTimeout = 20000; } catch { return; } while (true) { byte[] buffer = new byte[client.ReceiveBufferSize]; int read = 0; try { read = client.Receive(buffer); } catch { break; } if (read > 0) { //Handle data } else { break; } } if (client != null) client.Close(5000); } 

你在某个循环中调用AcceptClientAndProcess()。

以下行:

 read = client.Receive(buffer); 

将阻止,直到

  • 收到数据(读> 0),在这种情况下你可以处理它
  • 连接已正确关闭(读取= 0)
  • 连接突然关闭(抛出exception)

最后两种情况中的任何一种都表明客户端已不再连接。

还需要围绕Socket.Accept()方法的try catch,因为如果客户端连接在连接阶段突然关闭,它可能会失败。

请注意,确实为读取操作指定了20秒的超时。

NetworkStream.Read的文档没有反映这一点,但根据我的经验,如果端口仍处于打开状态并且没有数据可用,则“NetworkStream.Read”将阻塞,但如果端口已关闭,则返回0。

我从另一端遇到了这个问题 ,在那个NetworkStream.Read中,如果当前没有可用的数据,则不会立即返回0。 您必须使用NetworkStream.DataAvailable来确定NetworkStream.Read可以立即读取数据。