如何在C#中检测Socket断开连接

10

我正在处理一个客户端/服务器关系,旨在无限期地推送数据。

我试图解决的问题是在客户端方面,即我找不到一种方法来检测断开连接。

我尝试了其他人提供的几种解决方案,包括捕获IO异常,以及在所有三个SelectModes上轮询套接字。我还尝试结合使用轮询和检查套接字的“Available”字段。

// Something like this
Boolean IsConnected()
{
    try
    {
        bool part1 = this.Connection.Client.Poll(1000, SelectMode.SelectRead);
        bool part2 = (this.Connection.Client.Available == 0);

        if (part1 & part2)
        {
            // Never Occurs
            //connection is closed
            return false;
        }
        return true;
    }
    catch( IOException e )
    {
        // Never Occurs Either
    }
}
在服务器端,尝试向客户端写入一个“空”字符(\0)会导致IO异常,服务器可以检测到客户端已断开连接(非常容易)。在客户端上,同样的操作不会抛出异常。
// Something like this
Boolean IsConnected( )
{
    try
    {

        this.WriteHandle.WriteLine("\0");
        this.WriteHandle.Flush();
        return true;
    }
    catch( IOException e )
    {
        // Never occurs
        this.OnClosed("Yo socket sux");
        return false;
    }
}

我认为在通过轮询检测断开连接时遇到的问题是,如果自上次检查以来服务器还没有向客户端写回任何内容,那么我很容易遇到SelectRead返回false的情况...我不确定该怎么办,我已经追寻了每一个可用的选项来进行检测,但没有一种方法对我来说是100%有效的,最终我的目标是检测服务器(或连接)故障,通知客户端,等待重新连接等。所以你可以想象这是一个非常重要的部分。

感谢任何人的建议。 提前感谢您。

编辑:任何查看此问题的人都应注意下面的答案及我的最终评论。我已经详细阐述了如何解决这个问题,但还没有写成“问答”形式的帖子。


只需捕获IOException,并使用读取超时即可,您不需要所有这些其他的废话。 - user207421
我已经尝试过了(如果你没有真正阅读我的帖子...),但这是有时成功有时失败的。在一秒钟后超时的读取操作会导致IO,这将强制断开连接...但如果我只是没有收到数据呢...? - DigitalJedi805
我看了你的帖子。这不是“胡乱猜测”,而是受到本地和远程异步数据缓冲的影响。在第一次写入失败连接时,您不会收到异常,因为尚未检测到它:在TCP超时重试尝试之后,您将在随后的写入中收到异常。 - user207421
很抱歉我必须表示强烈反对。如果您愿意,我可以录制一个调试会话的Fraps视频... - DigitalJedi805
2
当远程一侧关闭连接时,Socket.Receive和EndReceive()会返回零。这在API描述中有记录:如果您正在使用面向连接的Socket,则Receive方法将读取尽可能多的数据,直到缓冲区的大小为止。如果远程主机使用Shutdown方法关闭Socket连接,并且所有可用数据都已接收,则Receive方法将立即完成并返回零字节。 - escape-llc
1个回答

13

一个选项是使用TCP保活包。您可以通过调用Socket.IOControl()来打开它们。唯一有点烦人的是它需要一个字节数组作为输入,因此您必须将数据转换为字节数组才能传递。以下是一个示例,使用10000ms保持活动状态并尝试1000ms重试:

Socket socket; //Make a good socket before calling the rest of the code.
int size = sizeof(UInt32);
UInt32 on = 1;
UInt32 keepAliveInterval = 10000; //Send a packet once every 10 seconds.
UInt32 retryInterval = 1000; //If no response, resend every second.
byte[] inArray = new byte[size * 3];
Array.Copy(BitConverter.GetBytes(on), 0, inArray, 0, size);
Array.Copy(BitConverter.GetBytes(keepAliveInterval), 0, inArray, size, size);
Array.Copy(BitConverter.GetBytes(retryInterval), 0, inArray, size * 2, size);
socket.IOControl(IOControlCode.KeepAliveValues, inArray, null);

只有当您没有发送其他数据时,才会发送保持活动数据包,因此每次发送数据时,10000毫秒的计时器都会被重置。


太棒了,谢谢Joel,我一到我的开发系统就会试试看。 - DigitalJedi805
嗨 Joel,我想我明白这里的想法,但如果我错了,请纠正我; IOControl方法在套接字上放置了一个定时交付的“KeepAlive”数据包。我将以上代码(几乎逐字)添加到了我的SocketServer.Client和SocketClient构造函数中。我将计时器缩短到一秒钟(因为这是两侧“读取”的超时时间?),但是我的StreamReader.ReadLine(当我打字时才意识到这不应该是我的策略)从未捕获任何数据......我必须说我不确定我们为什么要将我们要放入数组的内容放入其中;我可以在行末加上终止符吗? - DigitalJedi805
@DigitalJedi805 - 我相当确定保持连接已经在你看到任何东西之前被吃掉了。为了测试它,可以在不同的机器上运行你的应用程序,并运行Wireshark来查看数据。如果一切都连接正常,你应该能看到保持连接发送出去并收到回复,但是你的read()调用将不会看到任何东西。 - Joel Rondeau
好的,我将试一下,但我也很好奇 KeepAlive 是不是用来防止 ReadTimeout 发生的?如果我能通过这种方式达到目的,那么我将正好得到我需要的结果,但现在在双方都开启 KeepAlive 的情况下,我仍然会根据我的 ReadTimeout 异常。 - DigitalJedi805
2
@Carlos 没错。使用此功能时,无需调用SetSocketOption()。 - Joel Rondeau
显示剩余6条评论

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