当所有连接都断开时,启用了 KeepAlive 的 C# 套接字不会发送

C# Socket with KeepAlive enabled does not send when all connections are broken

我有一个打开只读 TCP 流套接字的应用程序。我已经设置了 KeepAlive 以确保每秒发送一条消息以检查连接是否仍然正常。 Wireshark 显示 KeepAlive 消息已发送,并且还被另一台计算机确认。我的申请是 运行 windows。

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

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

  1. 我已连接到互联网。 TCP Keep-Alive 每秒都在变强。我从机器上拔下 LAN 电缆。最后一个 TCP Keep-Alive 不是 ACKd,因此 Socket.Connectedfalse,正如预期的那样。
  2. 没有连接到互联网。 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);
}

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

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

当 TCP 连接建立时,会定期发送 Keep-alive。

therefore Socket.Connected is still true

看起来像是一个已知的 TCP 队首问题。套接字正在等待响应,直到发生超时被关闭。

想象一下设置:您处于糟糕的连接环境中,某些主机正在丢失数据包或挂起几秒钟。然后,如果 Socket 仅在未收到 keep-alive ping 时才会在超时之前关闭连接,您将获得一个关闭的连接。 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.