当所有连接断开时,启用 KeepAlive 的 C# Socket 不会发送

Posted

技术标签:

【中文标题】当所有连接断开时,启用 KeepAlive 的 C# Socket 不会发送【英文标题】:C# Socket with KeepAlive enabled does not send when all connections are broken 【发布时间】:2022-01-08 20:54:13 【问题描述】:

我有一个打开只读 TCP 流套接字的应用程序。我已设置 KeepAlive 以确保每秒发送一条消息以检查连接是否仍然正常。 Wireshark 显示 KeepAlive 消息是由另一台计算机发送和确认的。我的应用程序正在 Windows 上运行。

与网络/互联网的其余部分相比,我打开套接字的远程计算机位于单独的 LAN 适配器上。

有两种情况似乎以不同的方式做出反应,我想知道为什么。

    我已连接到互联网。 TCP Keep-Alive 每秒都在变强。我从机器上拔下 LAN 电缆。最后一个 TCP Keep-Alive 未 ACKd,因此 Socket.Connectedfalse,正如预期的那样。 我没有连接到互联网。 TCP Keep-Alive 每秒都在变强。我从机器上拔下 LAN 电缆。 TCP Keep-Alive 包完全发送到远程机器,因此Socket.Connected 仍然是true

当 TCP Keep-Alive 包由套接字发送时,我是否遗漏了一个重要概念?例如,如果您根本没有网络连接,就不要发送?如果是这样,我怎样才能确保套接字不再是Connected

_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.SetupKeepAlive();
_socket.Blocking = false;
public static void SetupKeepAlive(this Socket socket, uint interval = 1000U)

    if (socket is null)
    
        throw new ArgumentNullException(nameof(socket));
    

    // Get the size of the uint to use to back the byte array
    var size = Marshal.SizeOf(0U);

    // Create the byte array
    var keepAlive = new byte[size * 3];

    // Pack the byte array:
    // Turn keep-alive on
    Buffer.BlockCopy(BitConverter.GetBytes(1U), 0, keepAlive, 0, size);

    // Set amount of time without activity before sending a keep-alive
    Buffer.BlockCopy(BitConverter.GetBytes(interval), 0, keepAlive, size, size);

    // Set keep-alive interval
    Buffer.BlockCopy(BitConverter.GetBytes(interval), 0, keepAlive, size * 2, size);

    // Set the keep-alive settings on the underlying Socket
    _ = socket.IOControl(IOControlCode.KeepAliveValues, keepAlive, null);

【问题讨论】:

【参考方案1】:

Socket 不知道 Internet 连接的可用性,但它知道网络设备的状态。它只是一个非常底层的 TCP 单元。

它只能发送和接收不能诊断网络设备背后的网络的东西。我没有阅读 RFC,但您描述的行为是预期的。

在 TCP 连接建立时定期发送保持活动状态。

因此 Socket.Connected 仍然为真

看起来像一个已知的 TCP 线头问题。 Socket 正在等待响应,直到发生超时关闭。

想象一下设置:您处于较差的连接环境中,某些主机正在丢失数据包或挂起几秒钟。然后,如果 Socket 仅在未收到 keep-alive ping 时才会在 timout 之前关闭连接,那么您将获得一个关闭的连接。 Keep-alive 旨在使连接寿命更长而不是更短。

还有一个提示:

var keepAlive = new byte[12];
var values = MemoryMarshal.Cast<byte, int>(ref keepAlive);
values[0] = 1;
values[1] = interval; // make it int
values[2] = interval;
// the values were written directly to byte[] array

您似乎使用了错误的数字大小。数组大小预期为 12 字节 (3x4) 而不是 24 字节 (3x8)。可能您使用 C++ long 作为 C# long。但是 C++ long 在 C# 中是 int。 C# long 是 C++ long long

【讨论】:

感谢您的提示。我已经更改了我的代码。我也注意到出了什么问题。我在这个答案中读到:***.com/a/32244584/363224 有一些 10 次重试的硬编码重试机制。这意味着在拔下电缆时,1 秒的间隔可能会变成 10 秒的等待时间。我把间隔变小了,现在它可以工作了。 @Marnix keep-alive 间隔必须小于空闲超时,但尽可能接近。每秒 10 次 ping 是没有意义的,这会导致一堆无用的流量。例如空闲超时为 3 秒,则间隔可以为 2 秒,即2s + ping &lt; timeout。只是一个建议。 Keep-alive 只能防止空闲超时的连接关闭,没有别的。

以上是关于当所有连接断开时,启用 KeepAlive 的 C# Socket 不会发送的主要内容,如果未能解决你的问题,请参考以下文章

为啥当 XMPP 连接断开时,出席类型用户从不可用变为可用

当用户的数据连接因 tigase 断开时,我如何解决不获取 XMPP 状态更新的问题

当 TCP 连接中的链接断开时的 send() 函数行为

Xshell如何设置,当连接断开时保留Session,保留原文字

Flutter)我想在socket断开时自动重新连接

如何配置 NSURLSession 请求在连接断开时的重试尝试?