.Net Core 3.1 HttpClient 不重用套接字

.Net Core 3.1 HttpClient does not reuse sockets

我在 .Net 4.7.2 和 .Net Core 上 运行 有下面的代码,但我对每个框架都有不同的行为

public class Program
{


    private HttpClient Client = new HttpClient();
    public static async Task Main(string[] args)
    {
        Program example = new Program();

        Console.WriteLine("Starting connections");
        int numberofIterations = 10;
        Task<HttpResponseMessage>[] awaitableTasks = new Task<HttpResponseMessage>[numberofIterations];
        for (int i = 0; i < numberofIterations; i++)
        {
            var httpRequestMessage = new HttpRequestMessage();
            httpRequestMessage.RequestUri = new Uri("https://example.com");
            httpRequestMessage.Method = new HttpMethod("GET");

            awaitableTasks[i] = example.Client.SendAsync(httpRequestMessage);
            //Console.WriteLine(result.StatusCode);
        }

        Console.WriteLine("Connections done");
        await Task.WhenAll(awaitableTasks);

    }
}

使用 .Net Core 框架,网络跟踪显示每个请求都有一个单独的 tcp 连接,而使用 the.NEt 4.7.2 框架,套接字得到重用。

网络跟踪 .Net Core

网络跟踪 .Net 4.7.2

感谢您理解差异、解释此行为以及解决此问题的最佳方法的想法。

你看过这个资源吗:

Using HttpClientFactory without dependency injection

If you are on .NET Core - you should use a single HttpClient directly and set SocketsHttpHandler.PooledConnectionTimeout here to an appropriate value.

If you are on .NET Framework - you should use a single HttpClient and use ServicePoint to configure the similar settings.

The good news for anyone interested in connection management is that .NET now has reasonable behavior on Linux (as of 2.1 and SocketsHttpHandler) but it requires configuration.

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-3.1#alternatives-to-ihttpclientfactory-2

中找到更多详细信息

There are alternative ways to solve the preceding problems using a long-lived SocketsHttpHandler instance.

Create an instance of SocketsHttpHandler when the app starts and use it for the life of the app. Configure PooledConnectionLifetime to an appropriate value based on DNS refresh times. Create HttpClient instances using new HttpClient(handler, disposeHandler: false) as needed. The preceding approaches solve the resource management problems that IHttpClientFactory solves in a similar way.

The SocketsHttpHandler shares connections across HttpClient instances. This sharing prevents socket exhaustion. The SocketsHttpHandler cycles connections according to PooledConnectionLifetime to avoid stale DNS problems.

也可能相关(但更侧重于 DI):

Use IHttpClientFactory to implement resilient HTTP requests

以下是一些相关的摘录:

The original and well-known HttpClient class can be easily used, but in some cases, it isn't being properly used by many developers.

Though this class implements IDisposable, declaring and instantiating it within a using statement is not preferred because when the HttpClient object gets disposed of, the underlying socket is not immediately released, which can lead to a socket exhaustion problem. For more information about this issue, see the blog post You're using HttpClient wrong and it's destabilizing your software.

.....

HttpClient 生命周期

Each time you get an HttpClient object from the IHttpClientFactory, a new instance is returned. But each HttpClient uses an HttpMessageHandler that's pooled and reused by the IHttpClientFactory to reduce resource consumption, as long as the HttpMessageHandler's lifetime hasn't expired.

简短回答,下面对代码的修改将强制我的 .net 核心应用程序不创建两个以上的套接字并重用它们。

     var socketsHandler = new SocketsHttpHandler
            {
                PooledConnectionLifetime = TimeSpan.FromSeconds(60),
                PooledConnectionIdleTimeout = TimeSpan.FromMinutes(20),
                MaxConnectionsPerServer = 2
            };

     HttpClientHandler handler = new HttpClientHandler() { MaxConnectionsPerServer = 2 };

     var Client = new HttpClient(handler);

更新答案


更多详情:

.Net Framework 4.x.x HttpClient 实现建立在 HttpWebRequest 和 ServicePoint 之上,后者可由 ServicePointManager 管理。对于 ASP.NET 托管应用程序,ServicePointManager 的默认连接限制设置为 10,对于所有其他应用程序设置为 2,这就是为什么在我上面的示例中打开并重用 2 个套接字,因为应用程序被阻止创建超过 2 个连接(插座)每个服务点。

请参阅下面的参考资料以获得更多理解

https://docs.microsoft.com/en-us/dotnet/api/system.net.servicepointmanager.defaultconnectionlimit?view=net-6.0#system-net-servicepointmanager-defaultconnectionlimit

https://docs.microsoft.com/en-us/dotnet/api/system.net.servicepointmanager?view=net-6.0

https://docs.microsoft.com/en-us/dotnet/api/system.net.servicepoint?view=net-6.0

在 .Net Core 中,实现已多次更改,不再由 ServicePointManager 管理,并且没有默认连接限制。下面的文章有 HttpClient 的完整故事 Class。

https://www.stevejgordon.co.uk/httpclient-connection-pooling-in-dotnet-core

感谢@user700390、@PanagiotisKanavos 和@JeremyLakeman 为获得此问题的答案提供的帮助和指导。