一、前言

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

二、HttpClient使用中的那些坑

2.1 错误使用

using(var client = new HttpClient())

我们可以先来做一个简单的测试,代码如下:

 public async Task<string> 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;
}/// <summary>
///
/// </summary>
/// <param name="url"></param>
/// <returns></returns>
public async Task<string> GetBaiduAsync(string url)
{var client = _clientFactory.CreateClient();var result = await client.GetStringAsync(url);return result;
}

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

/// <summary>
/// Creates a new <see cref="HttpClient"/> using the default configuration.
/// </summary>
/// <param name="factory">The <see cref="IHttpClientFactory"/>.</param>
/// <returns>An <see cref="HttpClient"/> configured using the default configuration.</returns>
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 testsinternal 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 testsinternal virtual void StartHandlerEntryTimer(ActiveHandlerTrackingEntry entry){entry.StartExpiryTimer(_expiryCallback);}

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

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

往期精彩回顾

  • 【.net core】电商平台升级之微服务架构应用实战

  • .Net Core微服务架构技术栈的那些事

  • .net core 基于Dapper 的分库分表开源框架(core-data)

  • Asp.Net Core 中IdentityServer4 授权中心之应用实战

  • Asp.Net Core 中IdentityServer4 授权中心之自定义授权模式

  • Asp.Net Core 中IdentityServer4 授权流程及刷新Token

  • Asp.Net Core 中IdentityServer4 实战之 Claim详解

  • Asp.Net Core 中IdentityServer4 实战之角色授权详解

  • Asp.Net Core 中间件应用实战中你不知道的那些事

  • Asp.Net Core Filter 深入浅出的那些事-AOP

  • Asp.Net Core EndPoint 终结点路由工作原理解读

  • ASP.NET CORE 内置的IOC解读及使用

.net core HttpClient 使用之掉坑解析(一)相关推荐

  1. .net core HttpClient 使用之消息管道解析(二)

    一.前言 前面分享了 .net core HttpClient 使用之掉坑解析(一),今天来分享自定义消息处理HttpMessageHandler和PrimaryHttpMessageHandler ...

  2. Air202掉坑日记(3)——刷DTU固件(DTU版本)

    看过我之前文章的朋友都知道我是使用AT指令开发Air202的,但今天写的是DTU版本,为什么呢?因为AT开发对于我这种"半桶水"开发者来说坑有点多,在上报数据时,阿里云平台有正常接 ...

  3. docker springboot读取配置文件_Docker从入门到掉坑(三):容器太多,操作好麻烦

    前边的两篇文章里面,我们讲解了基于docker来部署基础的SpringBoot容器,如果阅读本文之前没有相关基础的话,可以回看之前的教程. Docker 从入门到掉坑​mp.weixin.qq.com ...

  4. 初次入坑解析的小程序(决定写代码风格的小程序)

    初次入坑解析的小程序(决定写代码风格的小程序) 我是一个大二的学生,在接触小程序之前我是一个学计算机运维的,后来入了坑,在入坑以后开发了两个小程序. 第一个是为学校开发的一个考勤的小程序,样式是别人设 ...

  5. docker mysql配置 丢失_Docker 从入门到掉坑

    Docker 介绍 简单的对docker进行介绍,可以把它理解为一个应用程序执行的容器.但是docker本身和虚拟机还是有较为明显的出入的.我大致归纳了一下,可以总结为以下几点: docker自身也有 ...

  6. docker pull 下载一半_Docker 从入门到掉坑

    Docker 介绍 简单的对docker进行介绍,可以把它理解为一个应用程序执行的容器.但是docker本身和虚拟机还是有较为明显的出入的.我大致归纳了一下,可以总结为以下几点: docker自身也有 ...

  7. .Net Core HttpClient处理响应压缩

    前言 在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采用哪种方 ...

  8. AlvinZH双掉坑里了

    AlvinZH双掉坑里了 时间限制: 1000 ms 内存限制: 65536 kb 题目描述 AlvinZH双掉进坑里了! 幸运的是,这坑竟然是宝藏迷宫的入口.这一次AlvinZH机智地带了很多很多背 ...

  9. Docker 从入门到掉坑

    Docker 介绍 简单的对docker进行介绍,可以把它理解为一个应用程序执行的容器.但是docker本身和虚拟机还是有较为明显的出入的.我大致归纳了一下,可以总结为以下几点: docker自身也有 ...

最新文章

  1. 微型计算机内存不能用指令修改的部分,在微型计算机内存储器中,不能用指令修改其存储内容的部分是什么?...
  2. AtCoder AGC004E Salvage Robots (DP)
  3. .net 连接php,NetBeans平台如何连接到PHP解析器?
  4. java 十 = 0_从零学java笔录-第10篇 数据类型(四)
  5. Almost All Divisors
  6. html分步调试,一般纯HTML网页的JavaScript的单步执行与调试(以VS2005)
  7. linux之shell脚本管理(一)
  8. python nan判断_Python数据分析:Numpy基本操作
  9. BZOJ1579 USACO 2009 Feb Gold 3.Revamping Trails Solution
  10. 凸优化第八章几何问题 8.5 中心
  11. 毕设题目:Matlab元胞自动机交通流
  12. 使用VS code编辑md文件,并导出pdf
  13. 微信小程序-制作购物车
  14. foobar2000在线标签服务器,不再烦恼 小烧友手把手教你设置 Foobar2000界面
  15. matlab freqz用法ba,【matlab】freqz函数的使用(一)
  16. Kconfig语法详解--结合示例
  17. idea英文翻译插件Translation
  18. 关于outlook不能发送126邮件的问题
  19. Java学习路线全过程(包含视频及图书资源)
  20. [转]4款手机音乐播放器对比

热门文章

  1. ThinkPHP讲解(十二)——文本编辑器和ajax传址
  2. Redis系统性介绍
  3. [zz]WCF分布式开发步步为赢(0):WCF学习经验分享,如何更好地学习WCF?
  4. java windows 取所有任务_Win下,通过Jstack截取Java进程中的堆栈信息
  5. 立即通过Xumo.TV在线观看免费电视
  6. NFC服务器在Linux,linux 安装 libnfc ,打开串口PN532
  7. mysql两种引擎的适用场景_MySQL两种引擎的区别和应用场景
  8. Linux日志出现大量kernel: NET: Registered protocol family 36
  9. 中航工业集团金网络(北京)电子商务有限公司副总经理刘正珩:航空“智”造的供应链支撑平台...
  10. Android系统匿名共享内存Ashmem(Anonymous Shared Memory)在进程间共享的原理分析