.net core HttpClient 使用之掉坑解析(一)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介:

.net core HttpClient 使用之掉坑解析(一)

一、前言
在我们开发当中经常需要向特定URL地址发送Http请求操作,在.net core 中对httpClient使用不当会造成灾难性的问题,这篇文章主要来分享.net core中通过IHttpClientFactory 工厂来使用HttpClient的正确打开方式。

二、HttpClient使用中的那些坑
2.1 错误使用
using(var client = new HttpClient())
我们可以先来做一个简单的测试,代码如下:

public async Task GetBaiduListAsync(string url)
{

 var html = "";
 for (var i = 0; i < 10; i++)
 {
      using (var client = new System.Net.Http.HttpClient())
      {
         var result=await client.GetStringAsync(url);
         html += result;
       }
 }
 return html;

}
运行项目输出结果后,通过netstate查看下TCP连接情况:

虽然项目已经运行结束,但是连接依然存在,状态为" TIME_WAIT"(继续等待看是否还有延迟的包会传输过来;默认在windows下,TIME_WAIT状态将会使系统将会保持该连接 240s。
在高并发的情况下,连接来不及释放,socket被耗尽,耗尽之后就会出现喜闻乐见的一个错误:

错误原因:
对象所占用资源应该确保及时被释放掉,但是,对于网络连接而言,这是错误的,原因有如下:

网络连接是需要耗费一定时间的,频繁开启与关闭连接,性能会受影响;
开启网络连接时会占用底层socket资源,但在HttpClient调用其本身的Dispose方法时,并不能立刻释放该资源,这意味着你的程序可能会因为耗尽连接资源而产生灾难性的问题。
对于上面的错误原因,大家可能会想到使用静态单例模式的HttpClient,如下:

private static HttpClient Client = new HttpClient();
静态单例模式虽然可以解决上面问题,但是会带来另外一个问题:

DNS变更会导致不能解析,DNS不会重新加载,需要重启才能变更(有兴趣的大佬可以去尝试一下)
三、正确使用及源码分析

HttpClientFactory 以模块化、可命名、可配置、弹性方式重建了 HttpClient 的使用方式: 由 DI 框架注入 IHttpClientFactory 工厂;由工厂创建 HttpClient 并从内部的 Handler 池分配请求 Handler。

.net core 2.1 开始引入了IHttpClientFactory 工厂类来自动管理IHttpClientFactory 类的创建和资源释放,可以通过Ioc 注入方式进行使用,代码如下:

services.AddControllers();
services.AddHttpClient();
调用代码如下:

private readonly IHttpClientFactory _clientFactory;

public FirstController(IHttpClientFactory clientFactory)
{

_clientFactory = clientFactory;

}

///
///
///
///
///
public async Task GetBaiduAsync(string url)
{

var client = _clientFactory.CreateClient();
var result = await client.GetStringAsync(url);
return result;

}
代码中通过IHttpClientFactory 中的CreateClient()方法进行创建一个HttpClient 对象,但是没有看到有释放资源的动作,那它是怎么释放的呢?
我们来看看它的主要源代码

///
/// Creates a new using the default configuration.
///
/// The .
/// An configured using the default configuration.
public static HttpClient CreateClient(this IHttpClientFactory factory)
{

 if (factory == null)
 {
    throw new ArgumentNullException(nameof(factory));
 }

 return factory.CreateClient(Options.DefaultName);

}

public HttpClient CreateClient(string name)
{

 if (name == null)
 {
      throw new ArgumentNullException(nameof(name));
 }

 var handler = CreateHandler(name);
 var client = new HttpClient(handler, disposeHandler: false);

 var options = _optionsMonitor.Get(name);
 for (var i = 0; i < options.HttpClientActions.Count; i++)
 {
     options.HttpClientActions[i](client);
 }

 return client;

}

public HttpMessageHandler CreateHandler(string name)
{

 if (name == null)
 {
     throw new ArgumentNullException(nameof(name));
 }

 var entry = _activeHandlers.GetOrAdd(name, _entryFactory).Value;

 StartHandlerEntryTimer(entry);

 return entry.Handler;

}
代码中可以看到创建HttpClent 时会先创建HttpMessageHandler对象,而CreateHandler 方法中调用了StartHandlerEntryTimer方法,该方法主要时启动清理释放定时器方法,核心代码如下:

public DefaultHttpClientFactory(

        IServiceProvider services,
        IServiceScopeFactory scopeFactory,
        ILoggerFactory loggerFactory,
        IOptionsMonitor<HttpClientFactoryOptions> optionsMonitor,
        IEnumerable<IHttpMessageHandlerBuilderFilter> filters)
    {
        if (services == null)
        {
            throw new ArgumentNullException(nameof(services));
        }

        if (scopeFactory == null)
        {
            throw new ArgumentNullException(nameof(scopeFactory));
        }

        if (loggerFactory == null)
        {
            throw new ArgumentNullException(nameof(loggerFactory));
        }

        if (optionsMonitor == null)
        {
            throw new ArgumentNullException(nameof(optionsMonitor));
        }

        if (filters == null)
        {
            throw new ArgumentNullException(nameof(filters));
        }

        _services = services;
        _scopeFactory = scopeFactory;
        _optionsMonitor = optionsMonitor;
        _filters = filters.ToArray();

        _logger = loggerFactory.CreateLogger<DefaultHttpClientFactory>();

        // case-sensitive because named options is.
        _activeHandlers = new ConcurrentDictionary<string, Lazy<ActiveHandlerTrackingEntry>>(StringComparer.Ordinal);
        _entryFactory = (name) =>
        {
            return new Lazy<ActiveHandlerTrackingEntry>(() =>
            {
                return CreateHandlerEntry(name);
            }, LazyThreadSafetyMode.ExecutionAndPublication);
        };

        _expiredHandlers = new ConcurrentQueue<ExpiredHandlerTrackingEntry>();
        _expiryCallback = ExpiryTimer_Tick;

        _cleanupTimerLock = new object();
        _cleanupActiveLock = new object();
    }

    // Internal for tests
    internal void ExpiryTimer_Tick(object state)
    {
        var active = (ActiveHandlerTrackingEntry)state;

        // The timer callback should be the only one removing from the active collection. If we can't find
        // our entry in the collection, then this is a bug.
        var removed = _activeHandlers.TryRemove(active.Name, out var found);
        Debug.Assert(removed, "Entry not found. We should always be able to remove the entry");
        Debug.Assert(object.ReferenceEquals(active, found.Value), "Different entry found. The entry should not have been replaced");

        // At this point the handler is no longer 'active' and will not be handed out to any new clients.
        // However we haven't dropped our strong reference to the handler, so we can't yet determine if
        // there are still any other outstanding references (we know there is at least one).
        //
        // We use a different state object to track expired handlers. This allows any other thread that acquired
        // the 'active' entry to use it without safety problems.
        var expired = new ExpiredHandlerTrackingEntry(active);
        _expiredHandlers.Enqueue(expired);

        Log.HandlerExpired(_logger, active.Name, active.Lifetime);

        StartCleanupTimer();
    }

    // Internal so it can be overridden in tests
    internal virtual void StartHandlerEntryTimer(ActiveHandlerTrackingEntry entry)
    {
        entry.StartExpiryTimer(_expiryCallback);
    }

从微软源码分析,HttpClient继承自HttpMessageInvoker,而HttpMessageInvoker实质就是HttpClientHandler。

HttpClientFactory 创建的HttpClient,也即是HttpClientHandler,只是这些个HttpClient被放到了“池子”中,工厂每次在create的时候会自动判断是新建还是复用。(默认生命周期为2min)。
希望这篇文章对你有帮助,如果对你有帮助请点个推荐,感谢!

如果您认为这篇文章还不错或者有所收获,您可以点击右下角的【推荐】按钮精神支持,因为这种支持是我继续写作,分享的最大动力!

作者:Jlion
原文地址https://www.cnblogs.com/jlion/p/12813692.html

相关文章
|
20天前
|
开发框架 .NET 开发者
简化 ASP.NET Core 依赖注入(DI)注册-Scrutor
Scrutor 是一个简化 ASP.NET Core 应用程序中依赖注入(DI)注册过程的开源库,支持自动扫描和注册服务。通过简单的配置,开发者可以轻松地从指定程序集中筛选、注册服务,并设置其生命周期,同时支持服务装饰等高级功能。适用于大型项目,提高代码的可维护性和简洁性。仓库地址:&lt;https://github.com/khellang/Scrutor&gt;
39 5
|
3月前
|
XML JSON API
ServiceStack:不仅仅是一个高性能Web API和微服务框架,更是一站式解决方案——深入解析其多协议支持及简便开发流程,带您体验前所未有的.NET开发效率革命
【10月更文挑战第9天】ServiceStack 是一个高性能的 Web API 和微服务框架,支持 JSON、XML、CSV 等多种数据格式。它简化了 .NET 应用的开发流程,提供了直观的 RESTful 服务构建方式。ServiceStack 支持高并发请求和复杂业务逻辑,安装简单,通过 NuGet 包管理器即可快速集成。示例代码展示了如何创建一个返回当前日期的简单服务,包括定义请求和响应 DTO、实现服务逻辑、配置路由和宿主。ServiceStack 还支持 WebSocket、SignalR 等实时通信协议,具备自动验证、自动过滤器等丰富功能,适合快速搭建高性能、可扩展的服务端应用。
171 3
|
2月前
|
开发框架 .NET C#
在 ASP.NET Core 中创建 gRPC 客户端和服务器
本文介绍了如何使用 gRPC 框架搭建一个简单的“Hello World”示例。首先创建了一个名为 GrpcDemo 的解决方案,其中包含一个 gRPC 服务端项目 GrpcServer 和一个客户端项目 GrpcClient。服务端通过定义 `greeter.proto` 文件中的服务和消息类型,实现了一个简单的问候服务 `GreeterService`。客户端则通过 gRPC 客户端库连接到服务端并调用其 `SayHello` 方法,展示了 gRPC 在 C# 中的基本使用方法。
45 5
在 ASP.NET Core 中创建 gRPC 客户端和服务器
|
28天前
|
开发框架 缓存 .NET
GraphQL 与 ASP.NET Core 集成:从入门到精通
本文详细介绍了如何在ASP.NET Core中集成GraphQL,包括安装必要的NuGet包、创建GraphQL Schema、配置GraphQL服务等步骤。同时,文章还探讨了常见问题及其解决方法,如处理复杂查询、错误处理、性能优化和实现认证授权等,旨在帮助开发者构建灵活且高效的API。
27 3
|
5天前
|
开发框架 算法 中间件
ASP.NET Core 中的速率限制中间件
在ASP.NET Core中,速率限制中间件用于控制客户端请求速率,防止服务器过载并提高安全性。通过`AddRateLimiter`注册服务,并配置不同策略如固定窗口、滑动窗口、令牌桶和并发限制。这些策略可在全局、控制器或动作级别应用,支持自定义响应处理。使用中间件`UseRateLimiter`启用限流功能,并可通过属性禁用特定控制器或动作的限流。这有助于有效保护API免受滥用和过载。 欢迎关注我的公众号:Net分享 (239字符)
24 0
|
3月前
|
测试技术 API 开发者
精通.NET单元测试:MSTest、xUnit、NUnit全面解析
【10月更文挑战第15天】本文介绍了.NET生态系统中最流行的三种单元测试框架:MSTest、xUnit和NUnit。通过示例代码展示了每种框架的基本用法和特点,帮助开发者根据项目需求和个人偏好选择合适的测试工具。
49 3
|
3月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3天前
|
监控 前端开发 API
一款基于 .NET MVC 框架开发、功能全面的MES系统
一款基于 .NET MVC 框架开发、功能全面的MES系统
|
4月前
|
开发框架 前端开发 JavaScript
ASP.NET MVC 教程
ASP.NET 是一个使用 HTML、CSS、JavaScript 和服务器脚本创建网页和网站的开发框架。
51 7
|
4月前
|
存储 开发框架 前端开发
ASP.NET MVC 迅速集成 SignalR
ASP.NET MVC 迅速集成 SignalR
85 0

推荐镜像

更多