【问题标题】:How to detect a Socket Disconnect in C#如何在 C# 中检测套接字断开连接
【发布时间】:2019-06-23 14:19:19
【问题描述】:

我正在处理客户端/服务器关系,该关系旨在在不确定的时间内来回推送数据。

我试图克服的问题是在客户端,因为我无法找到检测断开连接的方法。

我对其他人的解决方案进行了几次尝试,从仅捕获 IO 异常到在所有三个 SelectMode 上轮询套接字。我也尝试过结合使用投票,检查套接字的“可用”字段。

// 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 上遇到错误。 .. 不知道该怎么做,我已经找到了每个选项来进行我能找到的检测,但对我来说没有任何事情是 100% 的,最终我的目标是检测服务器(或连接)故障,通知客户端,等待重新连接等。所以我相信你可以想象这是一个不可或缺的部分。

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

编辑:查看此问题的任何人都应注意下面的答案,以及我对它的最终评论。我已经详细说明了我是如何克服这个问题的,但还没有发布“问答”风格的帖子。

【问题讨论】:

  • 只需捕获 IOExceptions,并使用读取超时。你不需要所有这些其他的恶意。
  • 我已经试过了(以防你没有真正阅读我的帖子......),它的命中或错过。一秒后读取操作超时会导致 IO,这将强制断开连接...但是如果我只是没有收到数据怎么办...?
  • 我读了你的帖子。它不是“命中注定”,它受到本地和远程异步数据缓冲的影响。在第一次写入失败的连接时不会出现异常,因为它尚未被检测到:在 TCP 已超时重试尝试之后,您将在后续写入时得到它。
  • 很抱歉,我非常不同意。如果您愿意,我可以进行调试会话...
  • Socket.Receive 和 EndReceive() 当远程端关闭时返回零。这在 API 描述中有记录:如果您使用面向连接的 Socket,则 Receive 方法将读取尽可能多的数据,直到缓冲区的大小。如果远程主机使用 Shutdown 方法关闭了 Socket 连接,并且已经接收到所有可用数据,则 Receive 方法将立即完成并返回零字节。

标签: c# sockets tcpclient streamwriter


【解决方案1】:

一种选择是使用 TCP 保持活动数据包。您可以致电Socket.IOControl() 将它们打开。唯一烦人的一点是它需要一个字节数组作为输入,因此您必须将数据转换为字节数组才能传入。这是一个使用 10000 毫秒保持活动并重试 1000 毫秒的示例:

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,我一定会在我打开我的开发系统后立即试一试。
  • 嘿乔尔,所以我想我明白了,但如果我错了,请纠正我; IOControl 方法将“KeepAlive”数据包的定时传送放在套接字上。我将上述代码(几乎是逐字逐句)添加到我的 SocketServer.Client 和 SocketClient 构造函数中。我将计时器减少到一秒(因为那是我双方“读取”的超时时间?)但我的 StreamReader.ReadLine (刚刚意识到我输入这个不应该是我的策略)永远不会捕获任何数据......我必须说我不确定我们为什么要将我们放入数组的内容;我可以终止它吗?
  • 实际上只是尝试使用 StreamReader.Read 调用运行相同的东西,但仍然没有在任何一个方向上得到任何东西。可能是我没有完全理解这个概念。
  • @DigitalJedi805 - 我有理由确定,在你看到任何东西之前,保持活力就被吃掉了。要对其进行测试,请在不同的机器上运行您的应用程序并运行 wireshark 来查看数据。您应该会看到 keep alive 熄灭并返回回复(如果一切都已连接),但您的 read() 调用将看不到任何内容。
  • @Carlos 没错。使用时,不需要调用 SetSocketOption()。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2012-12-10
  • 2020-07-04
  • 2011-09-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多