.NET Framework 4.7.2下Socket KeepAlive无效,Azure防火墙断连问题
TCP KeepAlive不生效问题排查
我有两个基于.NET Framework 4.7.2的应用程序:
- 第一个作为TCP客户端,代码如下:
client = new TcpClient(); client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); client.NoDelay = true; client.Connect(settings.Hostname, settings.Port);
- 第二个作为监听服务端,代码如下:
var listener = new TcpListener(settings.IPAddress, settings.Port); var listenerThread = new Thread(() => Listen(listener, cancellationTokenSource.Token)) { IsBackground = true, Priority = ThreadPriority.AboveNormal, }; private void Listen(TcpListener tcpListener, CancellationToken cancellationToken) { tcpListener.Start(); // stop listener on cancellation cancellationToken.Register(tcpListener.Stop); try { while (!cancellationToken.IsCancellationRequested) { var client = tcpListener.AcceptTcpClient(); client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); // Register client in background Task.Run(() => RegisterClientConnection(client), cancellationToken) .LogErrors(SystemLogManager.Current, "Error while registering client connection"); } } catch (SocketException) { // logging } }
读取流的逻辑:
using (var clientStream = tcpClient.GetStream()) ... bytesRead = await clientStream.ReadAsync(messageBuffer, messageBufferPointer, tcpClient.ReceiveBufferSize, cancellationToken);
奇怪的是,在Wireshark中看不到任何保活请求,且连接会在5分钟后被Azure防火墙断开。请问该场景中KeepAlive为何不生效?
原因及解决方法
1. Windows默认TCP KeepAlive参数触发延迟过长
仅设置SocketOptionName.KeepAlive = true只是开启了KeepAlive开关,但Windows系统默认的TCP KeepAlive参数为:
- 无数据交互后2小时发送第一个探测包
- 探测间隔1秒
- 重试5次
Azure防火墙闲置超时为5分钟,远短于默认的2小时触发时间,导致保活包尚未发送就被防火墙断开连接。
必须通过SIO_KEEPALIVE_VALS IO控制码手动配置具体参数,示例代码如下:
客户端配置:
client = new TcpClient(); client.NoDelay = true; const int SIO_KEEPALIVE_VALS = -1744830460; // 对应Winsock IOCTL码 var keepAliveBytes = new byte[12]; BitConverter.GetBytes((uint)1).CopyTo(keepAliveBytes, 0); // 启用KeepAlive BitConverter.GetBytes((uint)30000).CopyTo(keepAliveBytes, 4); // 5分钟后发送首个探测包(毫秒) BitConverter.GetBytes((uint)10000).CopyTo(keepAliveBytes, 8); // 探测间隔10秒(毫秒) client.Client.IOControl(SIO_KEEPALIVE_VALS, keepAliveBytes, null); client.Connect(settings.Hostname, settings.Port);
服务端配置:
var client = tcpListener.AcceptTcpClient(); // 先开启KeepAlive开关,再配置参数 client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); const int SIO_KEEPALIVE_VALS = -1744830460; var keepAliveBytes = new byte[12]; BitConverter.GetBytes((uint)1).CopyTo(keepAliveBytes, 0); BitConverter.GetBytes((uint)30000).CopyTo(keepAliveBytes, 4); BitConverter.GetBytes((uint)10000).CopyTo(keepAliveBytes, 8); client.Client.IOControl(SIO_KEEPALIVE_VALS, keepAliveBytes, null);
2. 应用层异步读取不影响TCP层保活机制
ReadAsync处于等待状态时,TCP栈会独立于应用层发送保活探测包,因此核心问题仍在于KeepAlive参数未正确配置,而非异步操作导致。
3. 确认Azure网络策略未拦截保活包
TCP保活探测包为TCP ACK包,使用原连接端口,Azure防火墙默认不会拦截此类数据包。若仍有疑问,可通过Wireshark在客户端/服务端本地捕获,确认保活包是否已发送,再排查网络层面问题。
内容的提问来源于stack exchange,提问作者Sheinar
相关产品推荐
相关产品推荐

