.Net HttpClient 使用准则

HttpClient 使用准则

System.Net.Http.HttpClient 类用于发送 HTTP 请求以及从 URI 所标识的资源接收 HTTP 响应。 HttpClient 实例是应用于该实例执行的所有请求的设置集合,每个实例使用自身的连接池,该池将其请求与其他请求隔离开来。

从 .NET Core 2.1 开始,SocketsHttpHandler 类提供实现,使行为在所有平台上保持一致。

准备工作:先执行下面单元,以启动WebApi及设置全局对象、方法及其它

//初始化:必须先执行一次
#!import ./ini.ipynb

统一使用示例

{ //大括号: 1、作用域隔离 2、方便整体代码折叠Console.WriteLine(global_api_config.BaseUrl);
}

启动WebApi

#启动已发布的WebApi项目
# 使用dotnet命令启动的程序,进程名均为 dotnet,不好关闭
# Start-Process -FilePath dotnet -ArgumentList ".\Publish\HttpClientStudy.WebApp\HttpClientStudy.WebApp.dll"# 此种,进程名固定
Start-Process -FilePath ".\Publish\HttpClientStudy.WebApp\HttpClientStudy.WebApp.exe"

关闭WebApi

# 关闭项目进程
$WebAppProcName ="HttpClientStudy.WebApp";
$WebAppProc = Get-Process $WebAppProcName -ErrorAction Ignore
if($null -eq $WebAppProc)
{Write-Host "进程没有找到,可能已经关闭"
}
else {$WebAppProc.Kill();Write-Host "$WebAppProcName 进程已退出"
}

1、DNS 行为

HttpClient 仅在创建连接时解析 DNS。它不跟踪 DNS 服务器指定的任何生存时间 (TTL)。

如果 DNS 条目定期更改(这可能在某些方案中发生),客户端将不会遵循这些更新。 要解决此问题,可以通过设置 PooledConnectionLifetime 属性来限制连接的生存期,以便在替换连接时重复执行 DNS 查找。

using System.Net.Http;
{var handler = new SocketsHttpHandler{// 15分钟PooledConnectionLifetime = TimeSpan.FromMinutes(15) };var sharedClient = new HttpClient(handler);sharedClient.Display();
}

上述 HttpClient 配置为重复使用连接 15 分钟。 PooledConnectionLifetime 指定的时间范围过后,系统会关闭连接,然后创建一个新连接。

2、共用连接(底层自动管理连接池)

HttpClient 的连接池链接到其基础 SocketsHttpHandler。
释放 HttpClient 实例时,它会释放池中的所有现有连接。 如果稍后向同一服务器发送请求,则必须重新创建一个新连接。
因此,创建不必要的连接会导致性能损失。
此外,TCP 端口不会在连接关闭后立即释放。 (有关这一点的详细信息,请参阅 RFC 9293 中的 TCP TIME-WAIT。)如果请求速率较高,则可用端口的操作系统限制可能会耗尽。

为了避免端口耗尽问题,建议将 HttpClient 实例重用于尽可能多的 HTTP 请求。

什么是连接池

SocketsHttpHandler为每个唯一端点建立连接池,您的应用程序通过HttpClient向该唯一端点发出出站HTTP请求。在对端点的第一个请求上,当不存在现有连接时,将建立一个新的HTTP连接并将其用于该请求。该请求完成后,连接将保持打开状态并返回到池中。

对同一端点的后续请求将尝试从池中找到可用的连接。如果没有可用的连接,并且尚未达到该端点的连接限制,则将建立新的连接。达到连接限制后,请求将保留在队列中,直到连接可以自由发送它们为止。

如何控制连接池

有三个主要设置可用于控制连接池的行为。

  • PooledConnectionLifetime,定义连接在池中保持活动状态的时间。此生存期到期后,将不再为将来的请求而合并或发出连接。

  • PooledConnectionIdleTimeout,定义闲置连接在未使用时在池中保留的时间。一旦此生存期到期,空闲连接将被清除并从池中删除。

  • MaxConnectionsPerServer,定义每个端点将建立的最大出站连接数。每个端点的连接分别池化。例如,如果最大连接数为2,则您的应用程序将请求发送到两个www.github.com和www.google.com,总共可能最多有4个打开的连接。

默认情况下,从.NET Core 2.1开始,更高级别的HttpClientHandler将SocketsHttpHandler用作内部处理程序。没有任何自定义配置,将应用连接池的默认设置。

PooledConnectionLifetime默认是无限的,因此,虽然经常使用的请求,连接可能会无限期地保持打开状态。该PooledConnectionIdleTimeout默认为2分钟,如果在连接池中长时间未使用将被清理。MaxConnectionsPerServer默认为int.MaxValue,因此连接基本上不受限制。

如果希望控制这些值中的任何一个,则可以手动创建SocketsHttpHandler实例,并根据需要进行配置。

//手动配置 SocketsHttpHandler
{var socketsHandler = new SocketsHttpHandler{PooledConnectionLifetime = TimeSpan.FromMinutes(10),PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),MaxConnectionsPerServer = 10};var client = new HttpClient(socketsHandler);client.Display();
}

在前面的示例中,对SocketsHttpHandler进行了配置,以使连接将最多在10分钟后停止重新发出并关闭。如果闲置5分钟,则连接将在池的清理过程中被更早地删除。我们还将最大连接数(每个端点)限制为十个。如果我们需要并行发出更多出站请求,则某些请求可能会排队等待,直到10个池中的连接可用为止。
要应用处理程序,它将被传递到HttpClient的构造函数中。

测试连接寿命

//测试连接寿命
{Console.WriteLine("程序运行大约要10-20秒,请在程序退出后,执行下面命令行查看网络情况");//自定义行为var socketsHandler = new SocketsHttpHandler{//连接池生命周期为10分钟:连接在池中保持活动时间为10分钟PooledConnectionLifetime = TimeSpan.FromMinutes(10),//池化链接的空闲超时时间为5分钟: 5分钟内连接不被重用,则被释放后销毁PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),//每端点的最大连接数设置为10个MaxConnectionsPerServer = 10};var client = new HttpClient(socketsHandler){BaseAddress = new Uri(global_api_config.BaseUrl)};var displayer = "".Display();for (var i = 0; i < 5; i++){if(i>0){await Task.Delay(TimeSpan.FromSeconds(2));}_ = await client.GetAsync(global_default_page);displayer.Update(($"第{i+1}次请求完成"));await Task.Delay(TimeSpan.FromSeconds(2));}
}

使用自定义设置,依次向同一端点发出5个请求。在每个请求之间,暂停两秒钟。输出从DNS检索到的网站服务器的IPv4地址。我们可以使用此IP地址来查看通过PowerShell中发出的netstat命令对其打开的连接:

# 若查询不到,则异常
#!set --value @csharp:global_netstat_filter --name queryFilterWrite-Host "请先执行上面的单元,再执行本单元"
Write-Host "网络状态"
netstat -ano | findstr $queryFilter

在这种情况下,到远程端点的连接只有1个。在每个请求之后,该连接将返回到池中,因此在发出下一个请求时可以重新使用。
如果更改连接的生存期,以使它们在1秒后过期,测试这对行为的影响:

//程序池设置
{  //自定义行为Console.WriteLine("程序运行大约要10-20,请在程序退出后,执行下面命令行查看网络情况");var socketsHandler2 = new SocketsHttpHandler{PooledConnectionLifetime = TimeSpan.FromSeconds(1),PooledConnectionIdleTimeout = TimeSpan.FromSeconds(1),MaxConnectionsPerServer = 1};var client2 = new HttpClient(socketsHandler2){BaseAddress = new Uri(global_api_config.BaseUrl)};var displayer = "".Display();for (var i = 0; i < 5; i++){if(i>0){await Task.Delay(TimeSpan.FromSeconds(2));}_ = await client2.GetAsync(global_default_page);displayer.Update(($"第{i+1}次请求完成"));await Task.Delay(TimeSpan.FromSeconds(2));}//调用命令行,显示查看网络情况string command = $"netstat -ano | findstr {global_netstat_filter}";// 创建一个新的ProcessStartInfo对象ProcessStartInfo startInfo = new ProcessStartInfo("cmd", $"/c {command}"){RedirectStandardOutput = true, // 重定向标准输出UseShellExecute = false, // 不使用系统外壳程序启动CreateNoWindow = true // 不创建新窗口};// 启动进程using (Process process = Process.Start(startInfo)){// 读取cmd的输出using (StreamReader reader = process.StandardOutput){string stdoutLine = reader.ReadToEnd();Console.WriteLine(stdoutLine);}}
}
#!set --value @csharp:global_netstat_filter --name queryFilter
netstat -ano | findstr $queryFilter

在这种情况下,我们可以看到使用了五个连接。其中的前四个在1秒后从池中删除,因此无法在下一个请求中重复使用。结果,每个请求都打开了一个新连接。现在,原始连接处于TIME_WAIT状态,并且操作系统无法将其重新用于新的出站连接。最终连接显示为ESTABLISHED,因为我在它过期之前就抓住了它。

测试最大连接数

/*功能:将MaxConnectionsPerServer限制为2。然后启动200个任务,每个任务都向同一端点发出HTTP请求。这些任务将同时运行。所有请求竞争所花费的时间将写入控制台。随即调用用netstat命令查看连接:则根据定义的限制,我们可以看到两个已建立的连接。
*/
{Console.WriteLine("开始请求网络...");var socketsHandler = new SocketsHttpHandler{PooledConnectionLifetime = TimeSpan.FromSeconds(60),PooledConnectionIdleTimeout = TimeSpan.FromMinutes(20),MaxConnectionsPerServer = 2};var client = new HttpClient(socketsHandler){BaseAddress = new Uri(global_api_config.BaseUrl)};var sw = Stopwatch.StartNew();var tasks = Enumerable.Range(0, 200).Select(i => client.GetAsync(global_default_page));await Task.WhenAll(tasks);sw.Stop();Console.WriteLine($"共请求了200次,耗时 {sw.ElapsedMilliseconds} 毫秒");//执行查看网络状态方法Console.WriteLine("当前网络状态");var message = HttpClientStudy.Core.Utilities.AppUtility.RunCmd($"netstat -ano | findstr {global_netstat_filter}");Console.WriteLine(message);
}
# 重新查询当前网络状态
#!set --value @csharp:global_netstat_filter --name queryFilter
netstat -ano | findstr $queryFilter

如果我们调整此代码以允许MaxConnectionsPerServer = 10,则可以重新运行该应用程序。耗时将减少大约4倍。

{   //MaxConnectionsPerServer 设置为10:网络连接将增加到10个,耗时将减少到1/4Console.WriteLine("开始请求网络...");var socketsHandler = new SocketsHttpHandler{PooledConnectionLifetime = TimeSpan.FromSeconds(60),PooledConnectionIdleTimeout = TimeSpan.FromMinutes(20),MaxConnectionsPerServer = 10};var client = new HttpClient(socketsHandler){BaseAddress = new Uri(global_api_config.BaseUrl)};//client.Display();var sw = Stopwatch.StartNew();var tasks = Enumerable.Range(0, 200).Select(i => client.GetAsync(global_default_page));await Task.WhenAll(tasks);sw.Stop();Console.WriteLine($"共请求了200次,耗时 {sw.ElapsedMilliseconds} 毫秒");//执行查看网络状态方法Console.WriteLine("当前网络状态");var message = AppUtility.RunCmd($"netstat -ano | findstr {global_netstat_filter}");Console.WriteLine(message);
}

3、推荐使用方式

总则:

一、 应使用长期客户端(静态对象、单例等),并设置 PooledConnectionLifetime。这能解决DNS问题和套接字耗尽问题。

二、 使用 IHttpClientFactory 创建的短期客户端:

  • 在 .NET Core 和 .NET 5+ 中:

    • 根据预期的 DNS 更改,使用 static 或 singletonHttpClient 实例,并将 PooledConnectionLifetime 设置为所需间隔(例如 2 分钟)。 这可以解决端口耗尽和 DNS 更改两个问题,而且不会增加 IHttpClientFactory 的开销。 如果需要模拟处理程序,可以单独注册它。

    • 使用 IHttpClientFactory,可以针对不同的用例使用多个以不同方式配置的客户端。 但请注意,工厂创建的客户端生存期较短,一旦创建客户端,工厂就不再可以控制它。
      工厂合并 HttpMessageHandler 实例,如果其生存期尚未过期,则当工厂创建新的 HttpClient 实例时,可以从池中重用处理程序。 这种重用避免了任何套接字耗尽问题。
      如果需要 IHttpClientFactory 提供的可配置性,我们建议使用类型化客户端方法。

  • 在 .NET Framework 中,使用 IHttpClientFactory 管理 HttpClient 实例。 如果不使用工厂,而是改为自行为每个请求创建新的客户端实例,则可能耗尽可用的端口。

提示: 如果应用需要 Cookie,请考虑禁用自动 Cookie 处理或避免使用 IHttpClientFactory。 共用 HttpMessageHandler 实例会导致共享 CookieContainer 对象。 意外的 CookieContainer 对象共享通常会导致错误的代码。

{ //不推荐的示例int requestCount =0;//这会建立10个 HttpClient //尽管使用了Using,不过Using只保证应用进程释放实例;但是http请求是跨操作系统、跨网络的操作,调用Using的进程管不了操作系统,更管不了网络。//如果把循环次数加大到 65535 就会一定导致夏套接字耗尽(2000以很可能就会出现)。Parallel.For(0,10,async (a,b)=>{using (var client = new HttpClient()){_ = await client.GetAsync (global_api_config.BaseUrl + global_default_page);}   Interlocked.Add(ref requestCount, 1);});
}{ //使用长期客户端using (var client = new HttpClient()){client.BaseAddress = new Uri(global_api_config.BaseUrl);for(int i=0; i<10; i++){//n次调用,均使用同一个 HttpClient 实例_ = await client.GetAsync(global_default_page);}}// 所有调用完成,才释放 HttpClient 实例
}

4、静态客户端的复原能力

#r "nuget:Polly"
#r "nuget:Microsoft.Extensions.Http.Resilience"
using System;
using System.Net.Http;
using Microsoft.Extensions.Http;
using Microsoft.Extensions.Http.Resilience;
using Polly;{var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>().AddRetry(new HttpRetryStrategyOptions{BackoffType = DelayBackoffType.Exponential,MaxRetryAttempts = 3}).Build();var socketHandler = new SocketsHttpHandler{PooledConnectionLifetime = TimeSpan.FromMinutes(15)};#pragma warning disable EXTEXP0001var resilienceHandler = new ResilienceHandler(retryPipeline){InnerHandler = socketHandler,};#pragma warning restore EXTEXP0001var httpClient = new HttpClient(resilienceHandler);httpClient.BaseAddress = new Uri(global_api_config.BaseUrl);var response = await httpClient.GetAsync(global_default_page);var htmlText = await response.Content.ReadAsStringAsync();Console.WriteLine($"共有{htmlText.Length}个字符");
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/82504.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【PostgreSQL】数据库主从库备份与高可用部署

文章目录 一、架构设计原理二、部署清单示例2.1 StatefulSet配置片段2.2 Service配置三、配置详解3.1 主节点postgresql.conf3.2 从节点配置四、初始化流程4.1 创建复制用户4.2 配置pg_hba.conf五、故障转移示例5.1 自动切换脚本5.2 手动提升从节点六、监控与维护6.1 关键监控指…

JavaScript 数组去重:11 种方法对比与实战指南

文章目录 前言一、使用 Set 数据结构二、使用 filter indexOf三、使用 reduce 累加器四、双重 for 循环五、利用对象属性唯一性六、先排序后去重七、使用 Map 数据结构八、使用 includes 方法九、优化处理 NaN 的 filter 方法十、利用 findIndex十一.利用Set和展开运算符处理多…

ai agent(智能体)开发 python3基础14:在python 中 总能看到方法里面套方法,那什么时候用这种方式合适呢?

让人头疼的方法嵌套还是要去了解的 在 Python 中&#xff0c;方法内部嵌套方法&#xff08;即在类的方法中定义另一个函数&#xff09;是一种常见的代码组织技巧&#xff0c;它可以在特定场景下带来以下好处&#xff1a; 1. 代码复用与逻辑封装 如果某个方法内部有重复的逻辑…

Yocto项目实战经验总结:从入门到高级的全面概览

本文面向开发者和实际项目经验者&#xff0c;分享经过大量实战积累的 Yocto 项目工程经验和基础技巧。本文简明但精彩&#xff0c;应用和观察相结合&#xff0c;充分适合做为全面进阶 Yocto 项目开发的实用指南。 一、入门理解&#xff1a;Yocto 是什么&#xff1f;规划如何开始…

添加物体.

在cesium中我们可以添加物体进入地图.我们以广州塔为例 //生成广州塔的位置var position2 Cesium.Cartesian3.fromDegrees(113.3191,23.109,100)viewer.camera.setView({//指定相机位置destination: position2, 运行后如图 我们使用cesium官网提供的代码为广州塔在地图上标点…

正则表达式非捕获分组?:

一个使用 Java 正则表达式的具体例子&#xff0c;展示了 (ab) 和 (?:ab) 的不同&#xff1a; 示例 1&#xff1a;使用 (ab)&#xff08;捕获分组&#xff09; import java.util.regex.*; public class RegexExample { public static void main(String[] args) { …

ragflow报错:KeyError: ‘\n “序号“‘

环境&#xff1a; ragflowv 0.17.2 问题描述&#xff1a; ragflow报错&#xff1a;KeyError: ‘\n “序号”’ **1. 推荐表&#xff08;输出json格式&#xff09;** [{"},{},{"},{} ]raceback (most recent call last): May 08 20:06:09 VM-0-2-ubuntu ragflow-s…

Spring Boot-8启动涉及的监听器(扩展点)

从出现时间上看&#xff1a; org.springframework.context.ApplicationListener&#xff0c;Spring 1.0开始出现 org.springframework.context.ApplicationContextInitializer&#xff0c;Spring 3.1开始出现 org.springframework.boot.SpringApplicationRunListener&#x…

如何启动vue项目及vue语法组件化不同标签应对的作用说明

如何启动vue项目及vue语法组件化不同标签应对的作用说明 提示&#xff1a;帮帮志会陆续更新非常多的IT技术知识&#xff0c;希望分享的内容对您有用。本章分享的是node.js和vue的使用。前后每一小节的内容是存在的有&#xff1a;学习and理解的关联性。【帮帮志系列文章】&…

思考:(linux) tmux 超级终端快速入门的宏观思维

tmux 工具集合 GitHub - rothgar/awesome-tmux: A list of awesome resources for tmux 要点&#xff1a; 习惯性思维的变换与宿主机之间的双向复制、粘贴手动备份全部窗口&#xff0c;以及还原自定义窗格提示信息TPM 插件的安装思想别名 在有些场景里&#xff0c;可能无法…

Python实例题:Python协程详解公开课

目录 Python实例题 题目 课程目标 课程内容规划 1. 课程开场&#xff08;5 分钟&#xff09; 2. 基础概念讲解&#xff08;15 分钟&#xff09; 并发与并行&#xff1a; 线程与进程&#xff1a; 3. Python 协程的实现方式&#xff08;20 分钟&#xff09; 生成器实现…

AI时代的数据可视化:未来已来

你有没有想过&#xff0c;数据可视化在未来会变成什么样&#xff1f;随着人工智能&#xff08;AI&#xff09;的飞速发展&#xff0c;数据可视化已经不再是简单的图表和图形&#xff0c;而是一个充满无限可能的智能领域。AI时代的可视化不仅能自动解读数据&#xff0c;还能预测…

强化学习PPO算法学习记录

1. 四个模型&#xff1a; Policy Model&#xff1a;我们想要训练的目标语言模型。我们一般用SFT阶段产出的SFT模型来对它做初始化。Reference Model&#xff1a;一般也用SFT阶段得到的SFT模型做初始化&#xff0c;在训练过程中&#xff0c;它的参数是冻结的。Ref模型的主要作用…

边缘计算从专家到小白

“云-边-端”架构 “云” &#xff1a;传统云计算的中心节点&#xff0c;是边缘计算的管控端。汇集所有边缘的感知数据、业务数据以及互联网数据&#xff0c;完成对行业以及跨行业的态势感知和分析。 “边” &#xff1a;云计算的边缘侧&#xff0c;分为基础设施边缘和设备边缘…

Windows:Powershell的使用

文章目录 零、格式化输出命令1、Format-List&#xff08;别名&#xff1a;fl&#xff09; 一、服务管理SC命令二、软件管理命令三、权限管理命令1、Get-Acl2、Set-Acl 总结 零、格式化输出命令 1、Format-List&#xff08;别名&#xff1a;fl&#xff09; 可通过管道符传递对象…

实现在h5中添加日历提醒:safari唤起系统日历,其它浏览器跳转google日历

需求&#xff1a;点击按钮后&#xff0c;将设定的一些信息插入到系统日历的日程安排中。 调研过程 先google了一段时间&#xff0c;了解该需求大概的实现方式。可以创建日历文件&#xff0c;在点击的时候下载该日历文件&#xff0c;看起来还比较复杂&#xff0c;并且由于不具…

【Bluedroid】蓝牙 HID 设备服务注册流程源码解析:从初始化到 SDP 记录构建

本文围绕蓝牙 HID&#xff08;人机接口设备&#xff09;服务注册流程&#xff0c;详细解析从 HID 服务启用、设备初始化、L2CAP 通道注册到 SDP&#xff08;服务发现协议&#xff09;记录构建的全流程。通过分析关键函数如btif_hd_service_registration、BTA_HdEnable、HID_Dev…

Win10无法上网:Windows 无法访问指定设备、路径或文件。你可能没有适当的权限访问该项目找不到域 TEST 的域控制器DNS 解析存在问题

目录 一.先看问题 二.解决问题 三.补充备用 一.先看问题 Win08有网且已经加入域 Win10无网并且找不到域&#xff08;说明&#xff1a;Win10我之前已经加入过域的&#xff0c;并且能够上网&#xff0c;但每次在宿舍和教室切换校园网&#xff0c;就会导致只有Win10无网&#…

M0基础篇之ADC

本节课使用到的例程 一、Single模式例程基本配置的解释 在例程中我们只使用到了PA25这一个通道&#xff0c;因此我们使用的是Single这个模式&#xff0c;也就是我们在配置模式的时候使用的是单一转换。 进行多个通道的测量我们可以使用Sequence这个模式。 二、Single模式例程基…

浅谈装饰模式

一、前言 hello大家好&#xff0c;本次打算简单聊一下装饰者模式&#xff0c;其实写有关设计模式的内容还是蛮有挑战性的&#xff0c;首先呢就是小永哥实力有限担心说不明白&#xff0c;其次设计模式是为了解决某些问题场景&#xff0c;在当前技术生态圈如此完善的情况下&#…