TcpListener: 如何检测客户端断开连接与客户端一段时间内未发送任何数据的区别

7
我正在寻找如何在使用TcpListener时检测“客户端断开连接”。
所有答案都与此类似: TcpListener:如何检测客户端断开连接? 基本上,从流中读取,如果Read()返回0,则表示客户端已断开连接。
但这是假设客户端在发送每个数据流后都会断开连接。 我们在TCP连接/断开开销慢且昂贵的环境中运作。
我们建立连接,然后发送多个请求。
伪代码:
client.Connect();
client.GetStatus();
client.DoSomething();
client.DoSomethingElse();
client.AndSoOn();
client.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
}

我在想...是否有更好的方法来检测客户端是否已经断开连接?


使用类似于 socket = listener.AcceptSocket(); s = new NetworkStream(socket); if (s.Read(..) == 0) ClientDisconnected(); 的代码似乎对我来说运行良好。 - undefined
3个回答

3

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

将会阻塞直到以下情况之一发生:

  • 数据被接收(read > 0),此时您可以处理它
  • 连接已经正常关闭(read = 0)
  • 连接已突然关闭(抛出异常)

最后两种情况表明客户端不再连接。

在 Socket.Accept() 方法周围加上 try catch 也是必需的,因为如果在连接阶段客户端连接意外关闭,则可能会失败。

请注意,指定了 20 秒的超时时间来进行读取操作。


3
您可以使用 NetworkStream.Socket 属性获得底层套接字,并使用其 Receive 方法进行读取。
NetworkStream.Read 不同,Socket.Receive 的链接重载方法将阻塞,直到读取指定数量的字节,并且仅在远程主机关闭 TCP 连接时返回零。

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


NetworkStream.Socket没有被TcpListener.GetStream()返回的NetworkStream实例公开。但是我现在正在研究直接使用Socket类。 - TimothyP
好的,直接使用套接字并使用Receive方法可以很好地工作。在接受客户端和尝试接收时需要使用try catch。谢谢! - TimothyP
为什么 .NET 在 TcpClientClient 属性中几乎不受限制地访问套接字,但却无法访问 NetworkStream.Socket?这是 .NET 设计的怪癖,还是存在无法获取 NetworkStream 套接字的情况?此外,你可能需要修改这个答案,因为 NetworkStream.Socket 是被保护的而且无法访问。 - jrh
1
@jrh,你关于protected属性的说法是正确的,我已经更新了答案。很可能,NetworkStream没有直接访问Socket,因为它提供了限制读写的能力。请参阅带有FileAccess参数的构造函数重载 - Eren Ersönmez

1

NetworkStream.Read的文档没有反映这一点,但根据我的经验,如果端口仍然打开且没有数据可用,则'NetworkStream.Read'会阻塞,但如果端口已关闭,则返回0。

我从另一个角度遇到了这个问题,即NetworkStream.Read如果当前没有数据可用,不会立即返回0。您必须使用NetworkStream.DataAvailable来查找NetworkStream.Read现在是否可以读取数据。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接