当所有连接断开时,启用 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.Connected
是 false
,正如预期的那样。
我没有连接到互联网。 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 < timeout
。只是一个建议。 Keep-alive 只能防止空闲超时的连接关闭,没有别的。以上是关于当所有连接断开时,启用 KeepAlive 的 C# Socket 不会发送的主要内容,如果未能解决你的问题,请参考以下文章
当用户的数据连接因 tigase 断开时,我如何解决不获取 XMPP 状态更新的问题