请求需要 5 分钟或更长时间时无响应

No response when request takes 5 minutes or longer

我遇到了一个非常奇怪的问题 - 当对特定 Web 服务器的请求需要 5 分钟或更长时间时,HttpClient 的 SendAsync 从不 returns。

这是一个示例 WebApi 控制器方法,我尝试从中获取响应

        [HttpGet]
        [Route("api/Entity/Ping")]
        public async Task<HttpResponseMessage> Ping([FromUri] int time)
        {
            await Task.Delay(TimeSpan.FromMinutes(time));
            var bytes = Enumerable.Repeat((byte)42, 100_000_000).ToArray();

            HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.OK);
            response.Content = new ByteArrayContent(bytes);
            response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            response.Content.Headers.ContentDisposition.FileName = "result.bin";
            response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");

            return response;
        }

这是发送请求的代码

            using (var client = HttpClientFactory.Create(handler))
            {
                client.Timeout = TimeSpan.FromMinutes(10);
                var url = "http://problem-server/WebApp/api/Entity/Ping?time=5";
                var request = new HttpRequestMessage
                {
                    Method = HttpMethod.Get,
                    RequestUri = new Uri(url)
                };
                var response = await client.SendAsync(
                    request,
                    HttpCompletionOption.ResponseHeadersRead,
                    default);

                var stream = await response.Content.ReadAsStreamAsync();
                if (response.IsSuccessStatusCode)
                    return stream;

                return default;
            }

如您所见,一切都非常简单,应该可以正常工作。但事实并非如此,SendAsync 调用永远挂起(10 分钟)。 同时它在 [time] 参数小于 5 时起作用。 此外,当您在浏览器中打开 URL 时,它会在处理 5 分钟后成功下载 result.bin 文件,因此方法有效。

起初我认为这是由于死锁造成的。 但是使用旧 WebRequest class 到相同 URL 的同步请求也挂起

            var url = "http://problem-server/WebApp/api/Entity/Ping?time=5";
            var request = WebRequest.Create(url);
            request.Timeout = (int)TimeSpan.FromMinutes(10).TotalMilliseconds;
            var response = request.GetResponse();
            var stream = response.GetResponseStream();
            if (stream != null)
                return stream;

            return default;

接下来,我将WebApp文件夹复制到另一台服务器,我们称之为ok-server。 修改了http客户端和web请求方法中的URLs。 而且,奇迹般地,一切正常 - [time] 分钟后收到响应。

所以问题出在问题服务器上。 但是如何调试\调查它 - IIS 请求跟踪或日志“说”请求在 [time] 分钟后成功完成并发送了响应。

两台机器,problem-server 和 ok-server,都有 IIS 8.5 和 Windows Server 2012 R2。 网络 Api 使用 .NET Framework 4.5。 (我还尝试将 .NET Core 3.1 与 ASP.NET Core 托管在 IIS for Web Api 上使用 - 结果是一样的)

你能帮我找到这个问题的原因吗? 也许,我需要查看全局机器配置或网络设置。

我现在真的迷路了。

更新

problem_server和ok_server在不同的网段。 problem_server IP 为 192.168.114.100,ok_server IP 为 192.150.0.15。 为了诊断可能的网络配置错误,我决定从机器的 IP 段中向 problem_server 发送请求。 这是从 192.168.114.125 机器执行测试客户端时的结果

我的工作站还在另一个 IP 段 - 192.135.9/24。也许在 192.150.0/24 和 192.135.9/24 段之间有一些路由器设置允许对 ok_server 的请求成功。

我真的建议您不要在 API 控制器中执行五分钟延迟。它会给你带来比它值得的更多的悲伤。例如,当 IIS 重新启动您的 AppPool 时,它将等待最多 90 秒来处理请求。在这些自主重启期间,此请求将被中止。

有问题的服务器可能将 TCP KeepAlive 设置为 Microsoft 推荐的(但不是默认值)5 分钟值。因为 HttpClient 默认不实现 TCP keepalive,问题服务器 OS 很可能在响应发送到客户端之前断开 TCP 套接字,因为客户端无法响应问题服务器发送的 keepalive OS.

您可以按照 here.

中所述编辑 HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Tcpip\Parameters\ 子项,在问题服务器的 OS 级别调整 TCP KeepAlive 设置

或者,您可以在 configuring the ServicePoint 发送请求之前将客户端配置为支持 TCP keepalive。如果客户端和服务器之间存在网络设备,例如状态防火墙,高频 keep-alive 设置可能有助于保持连接打开。

var sp = ServicePointManager.FindServicePoint(new Uri(url));
 sp.SetTcpKeepAlive(true, 6000, 3000);