I have the below piece of code which I ran on .Net 4.7.2 and .Net Core but I have got different behavior for each framework
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);
}
}
With the .Net Core framework, the network traces shows a separate tcp connection for each request while with the.NEt 4.7.2 framework the sockets get reused.
Network Trace .Net Core
Network Trace .Net 4.7.2
Appreciate your thoughts to understand the differences, to explain this behavior and the best way to overcome this issue.
CodePudding user response:
Short Answer, the below modification to the code will force my .net core app to not create more than two sockets and reuse them.
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);
Updated Answer
More Details:
The .Net Framework 4.x.x HttpClient implementation is built on top of HttpWebRequest and ServicePoint which can be managed by the ServicePointManager. The ServicePointManager has a default connection limit set to 10 for ASP.NET hosted applications and 2 for all others, and that is why in my above example the 2 sockets were opened and reused as the application were prevented to create more than 2 connections(sockets) per service point.
See the references below for a bit more understanding
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
C# Does Each Instance of HttpClient Get it's Own ServicePoint
In .Net Core, the implementation has been changed multiple times, no longer managed by the ServicePointManager and has no default connection limit. The below article has the full story of the HttpClient Class.
https://www.stevejgordon.co.uk/httpclient-connection-pooling-in-dotnet-core
Thanks to @user700390 and @PanagiotisKanavos and @JeremyLakeman for their help and guidance toward getting the answer to this question.
CodePudding user response:
Have you seen this resource:
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.
More detailed information found in 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.
Also possibly relevant (but more focused on DI):
Use IHttpClientFactory to implement resilient HTTP requests
Here are a few relevant excerpts:
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 lifetimes
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.