静态 HttpClient 仍在创建 TIME_WAIT 个 tcp 端口

Static HttpClient still creating TIME_WAIT tcp ports

我在使用 .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:有没有人知道如何解决这个问题,或者它可能是一个错误?

短版

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

长版

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

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

The default value for this property is false, which causes the current connection to be closed after a request is completed. Your application must go through the authentication sequence every time it issues a new request.

If this property is set to true, the connection used to retrieve the response remains open after the authentication has been performed. In this case, other requests that have this property set to true may use the connection without re-authenticating.

风险在于:

If a connection has been authenticated for user A, user B may reuse A's connection; user B's request is fulfilled based on the credentials of user A.

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

HttpClient _client;

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

WebRequestHandler 不会 公开允许按 ID 等对连接进行分组的 HttpWebRequest.ConnectionGroupName,因此它无法处理模拟。

.NET 核心 2.1

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

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