C# 在处理套接字耗尽和DNS回收时使用多个代理的HttpClient

C# 在处理套接字耗尽和DNS回收时使用多个代理的HttpClient,c#,.net-core,httpclient,time-wait,C#,.net Core,Httpclient,Time Wait,我们和一个朋友正在做一个有趣的项目,我们必须执行数百个HTTP请求,所有这些请求都使用不同的代理。想象一下,它是如下所示: for(int i=0;i

我们和一个朋友正在做一个有趣的项目,我们必须执行数百个HTTP请求,所有这些请求都使用不同的代理。想象一下,它是如下所示:

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);
  ...
}