【问题标题】:Static HttpClient still creating TIME_WAIT tcp ports静态 HttpClient 仍在创建 TIME_WAIT tcp 端口
【发布时间】:2019-01-09 22:18:54
【问题描述】:

我在使用 .NET Framework(4.5.1+、4.6.1 和 4.7.2)的 HttpClient 时遇到了一些有趣的行为。由于 TCP 端口使用率高的已知问题,我已提议在工作中的项目中进行一些更改,以便在每次使用时不处理 HttpClient,请参阅https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

我调查了更改以检查是否按预期工作,发现我们仍然遇到与以前相同的 TIME_WAIT 端口。

为了确认我提出的更改是正确的,我向应用程序添加了一些额外的跟踪,以确认我在整个应用程序中使用的是同一个 HttpClient 实例。从那以后,我使用了简单的测试应用程序(取自上面链接的 aspnetmonsters 站点。

using System;
using System.Net.Http;

namespace ConsoleApplication
{
    public class Program
    {
        private static HttpClientHandler { UseDefaultCredentials = true };
        private static HttpClient Client = new HttpClient(handler);
        public static async Task Main(string[] args) 
        {
            Console.WriteLine("Starting connections");
            for(int i = 0; i<10; i++)
            {
                var result = await Client.GetAsync("http://localhost:51000");
                Console.WriteLine(result.StatusCode);
            }
            Console.WriteLine("Connections done");
            Console.ReadLine();
        }
    }
}

仅在使用 Windows 身份验证连接到托管在 IIS 中的站点时才会出现此问题。我可以通过将身份验证设置为匿名(问题消失)并返回到 Windows 身份验证(问题再次发生)来轻松重现该问题。

Windows 身份验证问题似乎并不局限于提供程序的范围。如果您使用 Negotiate 或 NTLM,它也有同样的问题。如果机器只是工作站或域的一部分,也会出现此问题。

出于兴趣,我创建了一个 dotnet core 2.1.0 控制台应用程序,该问题根本不存在,并且按预期工作。

TLDR:有没有人知道如何解决这个问题,或者它可能是一个错误?

【问题讨论】:

  • 不存在“TCP 端口使用率高的已知问题”。那篇文章说您不需要 处理 HttpClient,因为它是可重用且线程安全的。它还说您不应该处置它,因为它可以像 WebClient 和原始 HttpWebRequest 那样重用 SSL 通道、套接字等。您使用静态 HttpClient 是因为可以,而不是因为任何问题
  • '没有“高 TCP 端口使用率的已知问题”'。那篇文章似乎以不同的方式论证了这一点。这不是错误,因为它是预期的行为,建议不要在每个请求中处理 HttpClient。甚至 Microsoft 模式和实践也推荐相同的配置。通过简单地使用 Windows 身份验证针对 IIS 运行示例应用程序,您可以看到问题。使用 sysinternals 中的 TcpView,您可以看到每个请求在 TIME_WAIT 中剩余 1 个端口。
  • 不,它没有。它并没有说 HttpClient 有问题 - 它甚至没有暗示它。它说您可以重用 HttpClient 实例并保存一些套接字。这也不是什么新鲜事,这是自 2012 年以来就为人所知的。不过,那篇文章可能是当今最受欢迎的文章
  • 顺便说一句,没有复制。运行文章的代码按预期运行。使用 TcpView 表明使用静态 HttpClient 不会留下任何套接字。每次使用新的 HttpClient 运行它确实会打开 10 个套接字。在使用静态客户端运行测试之前,我必须等待这些套接字关闭
  • 此外,使用静态客户端的性能要好得多,因为它不必每次都执行 DNS 查找和建立 SSL 通道。忘记插座。这是一个 100 倍的改进。 应该足以说服任何人使用共享的 HttpClient 实例,尤其是在您进行大量 HTTP 调用时

标签: c# .net-4.5


【解决方案1】:

短版

如果您想通过 NTLM 身份验证重用连接,请使用 .NET Core 2.1

加长版

当使用 NTLM 身份验证时,看到“旧”HttpClient 确实为每个请求使用不同的连接,我感到非常惊讶。这不是错误 - 在 .NET Core 2.1 之前,HttpClient 会使用 HttpWebRequest 在每次 NTLM 身份验证调用后关闭连接。

这在HttpWebRequest.UnsafeAuthenticatedConnectionSharing 属性的文档中有所描述,可用于启用连接共享:

此属性的默认值为 false,这会导致当前连接在请求完成后关闭。您的应用程序每次发出新请求时都必须经过身份验证序列。

如果此属性设置为 true,则用于检索响应的连接在执行身份验证后保持打开状态。在这种情况下,将此属性设置为 true 的其他请求可以使用该连接而无需重新进行身份验证。

风险在于:

如果一个连接已经被用户 A 认证,用户 B 可以重用 A 的连接;用户 B 的请求是根据用户 A 的凭据完成的。

如果了解风险,并且应用程序使用模拟,则可以使用WebRequestHandler 配置HttpClient 并设置UnsafeAuthenticatedConnectionSharing,例如:

HttpClient _client;

public void InitTheClient()
{
    var handler=new WebRequestHandler
                { 
                    UseDefaultCredentials=true,
                    UnsafeAuthenticatedConnectionSharing =true
                };
    _client=new HttpClient(handler); 
}

WebRequestHandler 公开HttpWebRequest.ConnectionGroupName,这将允许通过ID对连接进行分组,因此它无法处理模拟。

.NET Core 2.1

HttpClient 在 .NET Core 2.1 中进行了重写,并使用套接字实现了所有 HTTP、网络功能、最小分配、连接池等。它还单独处理 the NTLM challenge/response flow,因此可以使用相同的套接字连接来处理不同的经过身份验证的请求。

如果有人有兴趣,您可以追踪从 HttpClient 到 SocketsHttpHanlder 到 HttpConnectionPoolManager 、 HttpConnectionPool、 HttpConnection、 AuthenticationHelper.NtAuth 的调用,然后返回到 HttpConnection 以发送原始字节。

【讨论】:

  • 感谢您的回答,我可以看到最好的课程是迁移到.NET Core。我怀疑这是设计使然,而不是错误,不过很高兴知道。
  • 有见地的解释
猜你喜欢
  • 1970-01-01
  • 2011-08-01
  • 1970-01-01
  • 2020-10-19
  • 2010-09-25
  • 1970-01-01
  • 1970-01-01
  • 2013-04-07
  • 1970-01-01
相关资源
最近更新 更多