C# 在处理套接字耗尽和DNS回收时使用多个代理的HttpClient
我们和一个朋友正在做一个有趣的项目,我们必须执行数百个HTTP请求,所有这些请求都使用不同的代理。想象一下,它是如下所示:C# 在处理套接字耗尽和DNS回收时使用多个代理的HttpClient,c#,.net-core,httpclient,time-wait,C#,.net Core,Httpclient,Time Wait,我们和一个朋友正在做一个有趣的项目,我们必须执行数百个HTTP请求,所有这些请求都使用不同的代理。想象一下,它是如下所示: for(int i=0;i
for(int i=0;i<20;i++)
{
HttpClientHandler handler=newhttpclienthandler{Proxy=newwebproxy(randomProxy,true)};
使用(var客户端=新的HttpClient(处理程序))
{
使用(var request=newhttprequestmessage(HttpMethod.Get)http://x.com"))
{
var response=wait client.sendaync(请求);
if(响应。IsSuccessStatusCode)
{
string content=wait response.content.ReadAsStringAsync();
}
}
使用(var request2=newhttprequestmessage(HttpMethod.Get)http://x.com/news"))
{
var response=wait client.sendaync(request2);
if(响应。IsSuccessStatusCode)
{
string content=wait response.content.ReadAsStringAsync();
}
}
}
}
顺便说一下,我们使用的是.NETCore(目前为控制台应用程序)。我知道有很多关于套接字耗尽和处理DNS回收的线程,但这一个是不同的,因为有多个代理使用
如果我们使用HttpClient的单例实例,就像大家建议的那样:
- 我们不能设置多个代理,因为它是在HttpClient的实例化过程中设置的,以后不能更改
- 它不尊重DNS更改。重新使用HttpClient实例意味着它会一直保留套接字,直到它关闭。因此,如果服务器上发生DNS记录更新,客户端将永远不会知道,直到该套接字关闭。一种解决方法是将
标题设置为保持活动状态
,以便在每次请求后关闭套接字。这会导致次优性能。第二种方法是使用false
:ServicePoint
ServicePointManager.FindServicePoint(“http://x.com")
.ConnectionLeaseTimeout=Convert.ToInt32(TimeSpan.FromSeconds(15).total毫秒);
ServicePointManager.DnsRefreshTimeout=Convert.ToInt32(TimeSpan.FromSeconds(5).total毫秒);
另一方面,处理HttpClient(就像我上面的例子一样),换句话说,HttpClient的多个实例,会导致多个套接字处于TIME\u WAIT
状态。TIME_WAIT表示本地端点(此端)已关闭连接
我知道SocketsHttpHandler
和ihtpclientFactory
,但它们无法解决不同的代理
var socketsHandler=new SocketsHttpHandler
{
PooledConnectionLifetime=TimeSpan.FromMinutes(10),
PooledConnectionIdleTimeout=TimeSpan.FromMinutes(5),
MaxConnectionsPerServer=10
};
//无法为每个请求设置不同的代理
var client=新的HttpClient(socketsHandler);
能做出的最明智的决定是什么?将我的意见收集到了答案中。但这些都是改进建议,而不是解决方案,因为您的问题与上下文密切相关:有多少代理,每分钟有多少请求,每个请求的平均时间是多少,等等 Disclamer:我对IHttpClientFactory不太熟悉,但顺便说一句,这是解决套接字耗尽和DNS问题的唯一方法 注意:
ServicePointManager
不影响.NET Core中的HttpClient
,因为它用于.NET Core中的HttpWebRequest
而不是HttpClient
使用
正如@guruston所建议的,HttpClient
每个代理的实例看起来是合理的解决方案
HttpResponseMessage
是IDisposable
。对其应用using语句。它将影响套接字的使用行为
您可以将HttpCompletionOption.ResponseHeadersRead
应用于SendAsync
,因为在发送请求时没有读取整个响应。如果服务器返回不成功状态代码,则可能无法读取响应
为了提高内部性能,您还可以在sendsync()
和ReadAsStringAsync()
行中附加.ConfigureAwait(false)
。如果当前的SynchronizationContext
不是null
(例如,它不是控制台应用程序),那么它最有用
下面是一些经过优化的代码(C#8.0):
私有静态异步任务GetHttpResponseAsync(HttpClient客户端,字符串url)
{
使用HttpResponseMessage response=await client.GetAsync(url,HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if(响应。IsSuccessStatusCode)
{
返回wait response.Content.ReadAsStringAsync().configurewait(false);
}
返回null;
}
将池化的
HttpClient
和URL传递给该方法。重用HttpClient
实例(或者更具体地说,重用最后一个HttpMessageHandler
)的目的是重用套接字连接。不同的代理意味着不同的套接字连接,因此尝试在不同的代理上重用HttpClient
/HttpMessageHandler
是没有意义的,因为它必须是不同的连接
我们必须执行数百个HTTP请求,都使用不同的代理
如果每个请求都是真正唯一的代理,并且没有代理在任何其他请求之间共享,那么您最好只保留单个HttpClient
实例,并使用TIME\u WAIT
但是,如果多个请求可能通过同一个代理,并且您希望重用这些连接,那么这当然是可能的
我建议使用IHttpClientFactory
。它允许您定义命名的HttpClient
实例(从技术上讲,也是最后一个HttpMessageHandl
var proxies = new Dictionary<string, IWebProxy>(); // TODO: populate with proxies.
foreach (var proxy in proxies)
{
services.AddHttpClient(proxy.Key)
.ConfigurePrimaryHttpMessageHandler(() => new HttpClientHandler { Proxy = proxy.Value });
}
for (int i = 0; i < 20; i++)
{
var client = _httpClientFactory.CreateClient(randomProxyName);
...
}