在.NET中检测客户端断开连接的最佳实践是什么?

8
我正在使用C#开发一个服务器,只能接受一个客户端连接。我需要知道何时该客户端断开连接,以便能够接受其他连接请求。
我使用第一个Socket来持续监听连接请求,并使用Socket.BeginAccept接受或拒绝客户端。当客户端被接受时,返回的新Socket用于客户端和服务器之间的通信。然后,服务器使用Socket.Begin/EndReceive等待来自客户端的命令并发送响应。服务器使用类似Telnet的协议,这意味着每个命令和每行响应都必须以\r\n结尾。
为了检测客户端是否已经断开连接,我安装了一个定时器,每500毫秒向客户端发送一个空消息("\r\n")。如果客户端断开连接,则Socket会抛出异常。服务器捕获此异常,关闭当前会话并接受新连接。这种解决方案很稳健,但意味着不必要的网络流量,并且必须由客户端正确处理,在获取实际响应之前过滤虚假消息。
我尝试发送一个空缓冲区(Socket.Send(new byte[1], 0, 0)),但似乎在服务器->客户端方向上无法工作。
另一种解决方案可能是处理Socket.EndReceive返回0字节的情况。对于在“空闲”时间内发生的断开连接,它可以很好地工作。但是,如果客户端在消息传输期间断开连接,则服务器并不总是能够看到它,并且会无限期地等待。
我已经看到了几个关于此问题的线程和问题,但我从未见过任何好的解决方案。
因此,我的问题是:在.NET中检测断开连接的最佳方法是什么?

这种情况最让我恼火的是当你读取一个网络流并且它返回零时。这通常表示一个网络断开,但因为某些奇怪的原因,它不会引发异常。所以你很好地编写的 try-catch 块无法正常工作。我的解决方案是创建一个包装读取方法,在其中检查零返回并引发自己的异常。仍然觉得零返回应该引发异常。 - Bing Bang
4个回答

5
唯一的其他选择是,如果它是TCP,则需要定期发送TCP保持活动消息,这仍然是轮询,就像您现在所做的那样,但在TCP层处理,因此您的协议无需知道。
但是,没有轮询的方法,因为如果没有向另一个客户端发送任何内容并获得响应,您就无法知道它是否仍然连接着。
即使通过有状态数据包检查(例如标准NAPT)进行通信时,也可能需要保持活动以避免由于不活动而导致远程服务器丢弃会话。

好的,我已经添加了使我的套接字启用KeepAlive的代码。但是现在我应该期望什么?当客户端断开连接时什么也没有发生...我期望在某个地方收到异常...我应该定期读取或写入一些东西吗? - cedrou
1
你之前不是在使用Begin/EndReceive吗?如果是的话,BeginReceive应该会运行回调函数,当你调用EndReceive获取结果时,要么会抛出异常,要么就是读取到了流的末尾0字节。 - Chris Chilvers
好的,我现在得到了一个异常...谢谢 - cedrou

3
您可以使用以下方法来查找客户端是否仍然连接。这个方法会发送一个心跳包到客户端,如果客户端没有断开连接,则会返回true。代码如下:
public static bool IsConnected(this TcpClient client)
{
    try
    {
        bool connected = !(client.Client.Poll(1, SelectMode.SelectRead) && client.Client.Available == 0);

        return connected;
    }
    catch
    {
        return false;
    }
}

这个回答是关于测试socket的,我从中获得了我的代码片段。


2
如果客户端已经明确关闭了连接,那么这种方法就可以正常工作,但是如果连接被中断(例如电缆拔出),则无法正常工作。 - cedrou
这就是保持连接的作用,操作系统和TCP协议会定期向客户端发送消息以检查其是否存活,并轮询套接字以检查本地状态。如果保持连接失败,则套接字应报告其不再连接。 - Chris Chilvers
尽管使用异步Begin/EndReceive时不需要轮询,因为当套接字失败保持活动时,您应该收到带有错误回调的电话。 - Chris Chilvers
该方法存在竞态条件,导致误报,如此处所述。 - Ian

0

你能控制客户端吗?如果可以,你可以让客户端向服务器发送一个特殊的数据包来表示它正在断开连接,然后断开套接字。

你是想检测客户端是否已经断开连接(使用Socket.Shutdown()或Socket.Close()),还是客户端空闲了很长时间,因此需要被弹出?

无论哪种情况,都让客户端定期发送消息(就像你已经做的那样)到服务器。服务器可以跟踪最后一次心跳,如果客户端错过了3个以上的心跳,你就可以断开客户端。是的,这涉及到额外的数据,但在整个系统中并不太糟糕。如果你调整它,使它以足够好的周期发送心跳,这样你就可以知道客户端是否存活,并且不会在心跳之间花费太长时间,你就可以拥有一个非常好的系统。



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