c# - 在处理套接字耗尽和 DNS 回收时具有多个代理的 HttpClient
问题描述
我们正在和朋友一起做一个有趣的项目,我们必须执行数百个 HTTP 请求,所有这些请求都使用不同的代理。想象一下它是这样的:
for (int i = 0; i < 20; i++)
{
HttpClientHandler handler = new HttpClientHandler { Proxy = new WebProxy(randomProxy, true) };
using (var client = new HttpClient(handler))
{
using (var request = new HttpRequestMessage(HttpMethod.Get, "http://x.com"))
{
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
}
}
using (var request2 = new HttpRequestMessage(HttpMethod.Get, "http://x.com/news"))
{
var response = await client.SendAsync(request2);
if (response.IsSuccessStatusCode)
{
string content = await response.Content.ReadAsStringAsync();
}
}
}
}
顺便说一句,我们正在使用 .NET Core(目前是控制台应用程序)。我知道有很多关于套接字耗尽和处理 DNS 回收的线程,但是这个特定的线程是不同的,因为使用了多个代理。
如果我们使用 HttpClient 的单例实例,就像大家建议的那样:
- 我们不能设置多个代理,因为它是在 HttpClient 实例化期间设置的,之后无法更改。
- 它不尊重 DNS 更改。重用 HttpClient 的实例意味着它会保留套接字直到它关闭,因此如果您在服务器上发生 DNS 记录更新,客户端将永远不会知道,直到该套接字关闭。一种解决方法是将
keep-alive
标头设置为false
,以便在每次请求后关闭套接字。它导致次优性能。第二种方法是使用ServicePoint
:
ServicePointManager.FindServicePoint("http://x.com")
.ConnectionLeaseTimeout = Convert.ToInt32(TimeSpan.FromSeconds(15).TotalMilliseconds);
ServicePointManager.DnsRefreshTimeout = Convert.ToInt32(TimeSpan.FromSeconds(5).TotalMilliseconds);
另一方面,处理 HttpClient(就像我上面的例子一样),换句话说,HttpClient 的多个实例,会导致多个套接字处于TIME_WAIT
状态。TIME_WAIT 表示本地端点(这边)已经关闭了连接。
我知道SocketsHttpHandler
and IHttpClientFactory
,但他们无法解决不同的代理。
var socketsHandler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(10),
PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),
MaxConnectionsPerServer = 10
};
// Cannot set a different proxy for each request
var client = new HttpClient(socketsHandler);
可以做出的最明智的决定是什么?
解决方案
重用HttpClient
实例(或更具体地说,重用 last HttpMessageHandler
)的重点是重用套接字连接。不同的代理意味着不同的套接字连接,因此尝试在不同HttpClient
的代理上重用/是没有意义的,因为它必须是不同的连接。HttpMessageHandler
我们必须执行数百个 HTTP 请求,都使用不同的代理
如果每个请求都是真正的唯一代理,并且没有在任何其他请求之间共享代理,那么您最好保留单个HttpClient
实例并使用TIME_WAIT
.
但是,如果多个请求可能通过同一个代理,并且您想重用这些连接,那么这当然是可能的。
我建议使用IHttpClientFactory
. 它允许您定义可以合并和重用的命名HttpClient
实例(同样,技术上是最后一个实例)。HttpMessageHandler
只需为每个代理制作一个:
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 });
}
ConfigurePrimaryHttpMessageHandler
控制如何创建池化的IHttpClientFactory
主HttpMessageHandler
实例。我从您问题中的代码中复制HttpClientHandler
了,但大多数现代应用程序都使用SocketsHttpHandler
,它也具有Proxy
/UseProxy
属性。
然后,当您想使用一个时,调用IHttpClientFactory.CreateClient
并传递HttpClient
您想要的名称:
for (int i = 0; i < 20; i++)
{
var client = _httpClientFactory.CreateClient(randomProxyName);
...
}
推荐阅读
- c++ - 命名空间或类只有一个成员?
- c++ - 在派生类的基类构造函数中使用覆盖函数
- android - flutter 1.9.1+hotfix.2 - 构建 appbundle 失败,但实际上生成了一个 appbundle
- reactjs - Draft.js 如何表示项目列表?
- android - fetchSignInMethodsForEmail 在 TOAST 上给出一个空的电子邮件字段
- algorithm - 最宽路径问题有哪些应用?
- sql - Oracle中的日期比较出现错误的情况
- java - 如果某个应用程序处于焦点位置,则检查 Java
- python-datetime - 处理偏移天真的对象
- javascript - 谷歌云功能 - 承诺导致执行错误