如何避免 .NET 中 HttpClient 的 DNS 失效问题?

HttpClient 可以用来发送 HTTP 请求。HttpClient 可以设置为单例并在整个生命周期中重复使用。这是因为,HttpClient 有一个“连接池”来重用连接并减少 TCP 连接的数量。因此,如果您向同一主机发送多个请求,它们将重用相同的连接。这样,应用程序就不会在重负载下耗尽可用的套接字数量(如果使用 HttpClient 的方式有误,将会影响软件的稳定性)。此外,这种方式可以避免对同一主机的每个请求进行握手(TCP 握手、TLS 握手)从而提高应用程序的性能。

保持连接打开对提升性能有益处,但必须避免保持陈旧的连接。如果主机更改了 IP 地址怎么办?例如,如果 DNS TTL 过期,主机可能会更改其 IP 地址。在这种情况下,应该关闭已经打开的连接并打开一个新的。HttpClient 不会自动执行此操作,因为它感知不到 DNS TTL。尽管如此,可以通过设置超时时间来自动关闭链接。这样,下一个请求将需要重新打开连接,并使用 DNS 查找新的 IP 地址。

您可以使用 SocketsHttpHandler 来配置 HttpClient 及其连接池的行为。有 2 个属性需要配置:PooledConnectionIdleTimeout和PooledConnectionLifetime 。这些属性允许在一定时间后强制关闭连接。这样,下一个请求将打开一个新连接,以反映 DNS 或其他网络更改。

默认情况下,空闲连接会在 1 分钟后关闭。但是,活动连接永远不会关闭。因此必须明确设置PooledConnectionLifetime 为所需的值。

using System.Net;


using var socketHandler = new SocketsHttpHandler()
{
    // 池中连接的最大空闲时间。当达到最大空闲时间时,连接将会被释放。
    // 在 .NET 6 中,该属性的默认值是 1 分钟
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),


    //此属性定义池中连接的最大连接寿命,无论连接是空闲还是活动性。
    //在 .NET 6 中,该属性的默认值是从不过期
    //设置超时时间以反映 DNS 或其他网络更改
    PooledConnectionLifetime = TimeSpan.FromMinutes(1),
};


using var httpClient = new HttpClient(socketHandler);


var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
while (await timer.WaitForNextTickAsync())
{
    _ = await httpClient.GetStringAsync("https://www.coderbusy.com");
}

避免 DNS 问题的另一种方法是使用 IHttpClientFactory

如何调试

如果您想知道 HttpClient 何时查询 DNS,可以使用 EventListener 。这是因为 System.Net.* 命名空间下的对象会发出 ETW 追踪信息。

using System.Diagnostics.Tracing;


_ = new NetEventListener();


using var socketHandler = new SocketsHttpHandler()
{
    PooledConnectionIdleTimeout = TimeSpan.FromMinutes(1),
    PooledConnectionLifetime = TimeSpan.FromSeconds(10),
};


using var httpClient = new HttpClient(socketHandler);


var timer = new PeriodicTimer(TimeSpan.FromSeconds(2));
while (await timer.WaitForNextTickAsync())
{
    _ = await httpClient.GetStringAsync("https://www.coderbusy.com");
}


class NetEventListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (eventSource.Name.StartsWith("System.Net"))
            EnableEvents(eventSource, EventLevel.Informational);
    }
    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        if (eventData.EventName == "ResolutionStart")
        {
            Console.WriteLine(eventData.EventName + " - " + eventData.Payload[0]);
        }
        else if (eventData.EventName == "RequestStart")
        {
            Console.WriteLine(eventData.EventName + " - " + eventData.Payload[1]);
        }
    }
}

当您运行此应用程序时,您应该会看到应用程序何时执行 http 请求和 DNS 请求:

RequestStart - www.coderbusy.com
ResolutionStart - www.coderbusy.com
RequestStart - www.coderbusy.com
RequestStart - www.coderbusy.com
RequestStart - www.coderbusy.com
RequestStart - www.coderbusy.com
RequestStart - www.coderbusy.com
RequestStart - www.coderbusy.com
ResolutionStart - www.coderbusy.com
.NET框架中的HttpClient是一个用于处理HTTP请求和响应的高效、轻量级库。它提供了一种简单的方式来发送GET、POST、PUT等HTTP方法,并支持异步操作,非常适合网络通信场景。 **HttpClient的基本使用步骤:** 1. **创建实例:** ```csharp using HttpClient client = new HttpClient(); ``` 2. **设置基本配置(可选):** - 设置超时时间: ```csharp client.Timeout = TimeSpan.FromSeconds(5); ``` - 配置默认的代理服务器: ```csharp client.DefaultRequestHeaders.Proxy = WebProxy.Parse("http://your-proxy-server.com"); ``` 3. **发送请求:** - GET请求: ```csharp HttpResponseMessage response = await client.GetAsync("https://api.example.com/data"); ``` - POST请求: ```csharp var content = new StringContent("{'key': 'value'}", Encoding.UTF8, "application/json"); HttpResponseMessage response = await client.PostAsync("https://api.example.com/post", content); ``` 4. **检查响应状态并获取数据:** ```csharp if (response.IsSuccessStatusCode) { string responseBody = await response.Content.ReadAsStringAsync(); Console.WriteLine(responseBody); } else { // 处理错误情况 } ``` **示例:** ```csharp using System; using System.Net.Http; class Program { static async Task Main(string[] args) { try { using (HttpClient client = new HttpClient()) { HttpResponseMessage response = await client.GetAsync("https://jsonplaceholder.typicode.com/todos/1"); if (response.IsSuccessStatusCode) { string json = await response.Content.ReadAsStringAsync(); dynamic data = Newtonsoft.Json.JsonConvert.DeserializeObject(json); Console.WriteLine($"Todo ID: {data.id}, Title: {data.title}"); } else { Console.WriteLine($"Error: {response.StatusCode}"); } } } catch (Exception ex) { Console.WriteLine($"An error occurred: {ex.Message}"); } } } ``` 在这个例子中,我们向`jsonplaceholder.typicode.com`的一个API端点发送了一个GET请求,获取第一条todo信息,并将结果解析成动态JSON对象。 **相关问题--:** 1. HttpClient如何处理HTTP头? 2. 如何处理HttpClient的异常? 3. HttpClient是否线程安全?如果需要跨线程使用,应该如何处理?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值