This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.
{
"cells": [
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
}
"source": [
"# HttpClient 使用准则\n",
"System.Net.Http.HttpClient 类用于发送 HTTP 请求以及从 URI 所标识的资源接收 HTTP 响应。 HttpClient 实例是应用于该实例执行的所有请求的设置集合,每个实例使用自身的连接池,该池将其请求与其他请求隔离开来。 \n",
"\n",
"从 .NET Core 2.1 开始,SocketsHttpHandler 类提供实现,使行为在所有平台上保持一致。"
]
"vscode": {
"languageId": "polyglot-notebook"
"## 1、DNS 行为\n",
"HttpClient 仅在创建连接时解析 DNS。它不跟踪 DNS 服务器指定的任何生存时间 (TTL)。 \n",
"如果 DNS 条目定期更改(这可能在某些方案中发生),客户端将不会遵循这些更新。 要解决此问题,可以通过设置 PooledConnectionLifetime 属性来限制连接的生存期,以便在替换连接时重复执行 DNS 查找。"
"cell_type": "code",
"execution_count": null,
"outputs": [],
"using System.Net.Http;\n",
"var handler = new SocketsHttpHandler\n",
"{\n",
" // Recreate every 15 minutes\n",
" PooledConnectionLifetime = TimeSpan.FromMinutes(15) \n",
"};\n",
"var sharedClient = new HttpClient(handler);"
"metadata": {},
"上述 HttpClient 配置为重复使用连接 15 分钟。 PooledConnectionLifetime 指定的时间范围过后,系统会关闭连接,然后创建一个新连接。"
"## 2、共用连接(底层自动管理连接池)"
"HttpClient 的连接池链接到其基础 SocketsHttpHandler。 \n",
"释放 HttpClient 实例时,它会释放池中的所有现有连接。 如果稍后向同一服务器发送请求,则必须重新创建一个新连接。 \n",
"因此,创建不必要的连接会导致性能损失。 \n",
"此外,TCP 端口不会在连接关闭后立即释放。 (有关这一点的详细信息,请参阅 RFC 9293 中的 TCP TIME-WAIT。)如果请求速率较高,则可用端口的操作系统限制可能会耗尽。 \n",
"为了避免端口耗尽问题,建议将 HttpClient 实例重用于尽可能多的 HTTP 请求。"
"### 什么是连接池\n",
"SocketsHttpHandler为每个唯一端点建立连接池,您的应用程序通过HttpClient向该唯一端点发出出站HTTP请求。在对端点的第一个请求上,当不存在现有连接时,将建立一个新的HTTP连接并将其用于该请求。该请求完成后,连接将保持打开状态并返回到池中。\n",
"对同一端点的后续请求将尝试从池中找到可用的连接。如果没有可用的连接,并且尚未达到该端点的连接限制,则将建立新的连接。达到连接限制后,请求将保留在队列中,直到连接可以自由发送它们为止。"
"### 如何控制连接池"
"有三个主要设置可用于控制连接池的行为。\n",
"+ PooledConnectionLifetime,定义连接在池中保持活动状态的时间。此生存期到期后,将不再为将来的请求而合并或发出连接。\n",
"+ PooledConnectionIdleTimeout,定义闲置连接在未使用时在池中保留的时间。一旦此生存期到期,空闲连接将被清除并从池中删除。\n",
"+ MaxConnectionsPerServer,定义每个端点将建立的最大出站连接数。每个端点的连接分别池化。例如,如果最大连接数为2,则您的应用程序将请求发送到两个www.github.com和www.google.com,总共可能最多有4个打开的连接。\n",
"默认情况下,从.NET Core 2.1开始,更高级别的HttpClientHandler将SocketsHttpHandler用作内部处理程序。没有任何自定义配置,将应用连接池的默认设置。\n",
"该**PooledConnectionLifetime默认是无限的,因此,虽然经常使用的请求,连接可能会无限期地保持打开状态。该PooledConnectionIdleTimeout默认为2分钟,如果在连接池中长时间未使用将被清理。MaxConnectionsPerServer**默认为int.MaxValue,因此连接基本上不受限制。\n",
"如果希望控制这些值中的任何一个,则可以手动创建SocketsHttpHandler实例,并根据需要进行配置。"
"var socketsHandler = new SocketsHttpHandler\n",
"\tPooledConnectionLifetime = TimeSpan.FromMinutes(10),\n",
"\tPooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),\n",
"\tMaxConnectionsPerServer = 10\n",
"\t\n",
"var client = new HttpClient(socketsHandler);"
"在前面的示例中,对SocketsHttpHandler进行了配置,以使连接将最多在10分钟后停止重新发出并关闭。如果闲置5分钟,则连接将在池的清理过程中被更早地删除。我们还将最大连接数(每个端点)限制为十个。如果我们需要并行发出更多出站请求,则某些请求可能会排队等待,直到10个池中的连接可用为止。\n",
"要应用处理程序,它将被传递到HttpClient的构造函数中。"
"### 测试连接寿命"
"using System.Net;\n",
"var ips = await Dns.GetHostAddressesAsync(\"www.hao123.com\");\n",
"string firstIp = ips.FirstOrDefault().ToString();\n",
"foreach (var ipAddress in ips)\n",
" Console.WriteLine(ipAddress.MapToIPv4().ToString());\n",
"}\n",
"//自定义行为\n",
" //连接池生命周期为10分钟:连接在池中保持活动时间为10分钟\n",
" PooledConnectionLifetime = TimeSpan.FromMinutes(10),\n",
" //池化链接的空闲超时时间为5分钟: 5分钟内连接不被重用,则被释放后销毁\n",
" PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),\n",
" \n",
" //每端点的最大连接数设置为10个\n",
" MaxConnectionsPerServer = 10\n",
"var client = new HttpClient(socketsHandler);\n",
"for (var i = 0; i < 5; i++)\n",
" _ = await client.GetAsync(\"https://www.hao123.com\");\n",
" await Task.Delay(TimeSpan.FromSeconds(2));\n",
"Console.WriteLine(\"请在程序退出后,执行下面命令行查看网络情况\");\n"
"使用我们刚刚讨论的设置,此代码依次向同一端点发出5个请求。在每个请求之间,它会暂停两秒钟。该代码还输出从DNS检索到的Google服务器的IPv4地址。我们可以使用此IP地址来查看通过PowerShell中发出的netstat命令对其打开的连接:"
"language": "pwsh"
"kernelName": "pwsh"
"#!set --value @csharp:firstIp --name queryIp\n",
"Write-Host \"请先执行上面的单元,再执行本单元\"\n",
"Write-Host \"异常话,很可能是:未查找IP为 $queryIp 的网络状\"\n",
"netstat -ano | findstr $queryIp"
"## 3、推荐使用方式"
"source": []
"#!set --value @csharp:ips --name ips\n"
"#!set --value @csharp:firstIp --name firstIp\n"
"language": "javascript"
"kernelName": "javascript"
"#!set --value @pwsh:$sss --name $sss\n"
],
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
"language_info": {
"name": "python"
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
"aliases": [],
"name": "csharp"
"nbformat": 4,
"nbformat_minor": 2