You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

829 lines
27 KiB
Plaintext

This file contains ambiguous Unicode characters!

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 类提供实现,使行为在所有平台上保持一致。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 准备工作先执行下面单元以启动WebApi及设置全局对象、方法及其它"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"配置文件根目录c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.Core\n",
"程序[c:\\Users\\ruyu\\Desktop\\HttpClientStudy\\Docs\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe]已在新的命令行窗口执行。如果未出现新命令行窗口,可能是程序错误造成窗口闪现!\n"
]
}
],
"source": [
"//全局设置\n",
"#r \"./Publish/HttpClientStudy.Core/HttpClientStudy.Core.dll\"\n",
"\n",
"//全局命名空间引用\n",
"global using System.Net;\n",
"global using System.Net.Http;\n",
"global using System.Diagnostics;\n",
"global using System.Collections;\n",
"global using System.Text;\n",
"global using System.Linq;\n",
"global using System.IO;\n",
"global using System.Threading;\n",
"global using System.Threading.Tasks;\n",
"\n",
"global using HttpClientStudy.Core;\n",
"global using HttpClientStudy.Core.Utilities;\n",
"\n",
"//全局共享变量\n",
"var global_api_config = HttpClientStudy.Config.WebApiConfigManager.GetWebApiConfig();\n",
"var global_ips = Dns.GetHostAddresses(global_api_config.Host);\n",
"var global_queryIp = global_ips.First().ToString();\n",
"if(global_api_config.Host.Contains(\"localhost\"))\n",
"{\n",
" global_queryIp = \"127.0.0.1\";\n",
"}\n",
"var global_default_page = $\"{global_api_config.PathBase}/Normal/GetAllAccounts\";\n",
"//启动WebAPI程序\n",
"var global_netstat_filter = $\"{global_queryIp}:{global_api_config.Port}\";\n",
"var global_webapi_file = Path.GetFullPath(\"./Publish/HttpClientStudy.WebApp/HttpClientStudy.WebApp.exe\", Environment.CurrentDirectory); \n",
"var message = AppUtility.RunWebApiExeFile(global_webapi_file);\n",
"Console.WriteLine(message);"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"统一使用示例"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"{ //大括号: 1、作用域隔离 2、方便整体代码折叠\n",
" Console.WriteLine(global_api_config.BaseUrl);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 启动WebApi"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "pwsh"
},
"polyglot_notebook": {
"kernelName": "pwsh"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"#启动已发布的WebApi项目\n",
"# 使用dotnet命令启动的程序进程名均为 dotnet不好关闭\n",
"# Start-Process -FilePath dotnet -ArgumentList \".\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.dll\"\n",
"\n",
"# 此种,进程名固定\n",
"Start-Process -FilePath \".\\Publish\\HttpClientStudy.WebApp\\HttpClientStudy.WebApp.exe\""
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 关闭WebApi"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "pwsh"
},
"polyglot_notebook": {
"kernelName": "pwsh"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"# 关闭项目进程\n",
"$WebAppProcName =\"HttpClientStudy.WebApp\";\n",
"$WebAppProc = Get-Process $WebAppProcName -ErrorAction Ignore\n",
"if($null -eq $WebAppProc)\n",
"{\n",
" Write-Host \"进程没有找到,可能已经关闭\"\n",
"}\n",
"else {\n",
" $WebAppProc.Kill();\n",
" Write-Host \"$WebAppProcName 进程已退出\"\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"source": [
"## 1、DNS 行为\n",
"HttpClient 仅在创建连接时解析 DNS。它不跟踪 DNS 服务器指定的任何生存时间 (TTL)。 \n",
"\n",
"如果 DNS 条目定期更改(这可能在某些方案中发生),客户端将不会遵循这些更新。 要解决此问题,可以通过设置 PooledConnectionLifetime 属性来限制连接的生存期,以便在替换连接时重复执行 DNS 查找。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"using System.Net.Http;\n",
"{\n",
" var handler = new SocketsHttpHandler\n",
" {\n",
" // 15分钟\n",
" PooledConnectionLifetime = TimeSpan.FromMinutes(15) \n",
" };\n",
" var sharedClient = new HttpClient(handler);\n",
"\n",
" sharedClient.Display();\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"上述 HttpClient 配置为重复使用连接 15 分钟。 PooledConnectionLifetime 指定的时间范围过后,系统会关闭连接,然后创建一个新连接。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2、共用连接(底层自动管理连接池)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"HttpClient 的连接池链接到其基础 SocketsHttpHandler。 \n",
"释放 HttpClient 实例时,它会释放池中的所有现有连接。 如果稍后向同一服务器发送请求,则必须重新创建一个新连接。 \n",
"因此,创建不必要的连接会导致性能损失。 \n",
"此外TCP 端口不会在连接关闭后立即释放。 (有关这一点的详细信息,请参阅 RFC 9293 中的 TCP TIME-WAIT。如果请求速率较高则可用端口的操作系统限制可能会耗尽。 \n",
"\n",
"为了避免端口耗尽问题,建议将 HttpClient 实例重用于尽可能多的 HTTP 请求。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 什么是连接池\n",
"SocketsHttpHandler为每个唯一端点建立连接池您的应用程序通过HttpClient向该唯一端点发出出站HTTP请求。在对端点的第一个请求上当不存在现有连接时将建立一个新的HTTP连接并将其用于该请求。该请求完成后连接将保持打开状态并返回到池中。\n",
"\n",
"对同一端点的后续请求将尝试从池中找到可用的连接。如果没有可用的连接,并且尚未达到该端点的连接限制,则将建立新的连接。达到连接限制后,请求将保留在队列中,直到连接可以自由发送它们为止。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 如何控制连接池"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"有三个主要设置可用于控制连接池的行为。\n",
"\n",
"+ PooledConnectionLifetime定义连接在池中保持活动状态的时间。此生存期到期后将不再为将来的请求而合并或发出连接。\n",
"\n",
"+ PooledConnectionIdleTimeout定义闲置连接在未使用时在池中保留的时间。一旦此生存期到期空闲连接将被清除并从池中删除。\n",
"\n",
"+ MaxConnectionsPerServer定义每个端点将建立的最大出站连接数。每个端点的连接分别池化。例如如果最大连接数为2则您的应用程序将请求发送到两个www.github.com和www.google.com总共可能最多有4个打开的连接。\n",
"\n",
"默认情况下,从.NET Core 2.1开始更高级别的HttpClientHandler将SocketsHttpHandler用作内部处理程序。没有任何自定义配置将应用连接池的默认设置。\n",
"\n",
"该**PooledConnectionLifetime默认是无限的因此虽然经常使用的请求连接可能会无限期地保持打开状态。该PooledConnectionIdleTimeout默认为2分钟如果在连接池中长时间未使用将被清理。MaxConnectionsPerServer**默认为int.MaxValue因此连接基本上不受限制。\n",
"\n",
"如果希望控制这些值中的任何一个则可以手动创建SocketsHttpHandler实例并根据需要进行配置。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"//手动配置 SocketsHttpHandler\n",
"{\n",
"\tvar socketsHandler = new SocketsHttpHandler\n",
"\t{\n",
"\t\tPooledConnectionLifetime = TimeSpan.FromMinutes(10),\n",
"\t\tPooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),\n",
"\t\tMaxConnectionsPerServer = 10\n",
"\t};\n",
"\t\t\n",
"\tvar client = new HttpClient(socketsHandler);\n",
"\n",
"\tclient.Display();\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在前面的示例中对SocketsHttpHandler进行了配置以使连接将最多在10分钟后停止重新发出并关闭。如果闲置5分钟则连接将在池的清理过程中被更早地删除。我们还将最大连接数每个端点限制为十个。如果我们需要并行发出更多出站请求则某些请求可能会排队等待直到10个池中的连接可用为止。\n",
"要应用处理程序它将被传递到HttpClient的构造函数中。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 测试连接寿命"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"//测试连接寿命\n",
"{\n",
" Console.WriteLine(\"程序运行大约要10-20秒请在程序退出后执行下面命令行查看网络情况\");\n",
"\n",
" //自定义行为\n",
" var socketsHandler = new SocketsHttpHandler\n",
" {\n",
" //连接池生命周期为10分钟连接在池中保持活动时间为10分钟\n",
" PooledConnectionLifetime = TimeSpan.FromMinutes(10),\n",
"\n",
" //池化链接的空闲超时时间为5分钟: 5分钟内连接不被重用则被释放后销毁\n",
" PooledConnectionIdleTimeout = TimeSpan.FromMinutes(5),\n",
" \n",
" //每端点的最大连接数设置为10个\n",
" MaxConnectionsPerServer = 10\n",
" };\n",
"\n",
" var client = new HttpClient(socketsHandler)\n",
" {\n",
" BaseAddress = new Uri(global_api_config.BaseUrl)\n",
" };\n",
"\n",
" var displayer = \"\".Display();\n",
"\n",
" for (var i = 0; i < 5; i++)\n",
" {\n",
" if(i>0)\n",
" {\n",
" await Task.Delay(TimeSpan.FromSeconds(2));\n",
" }\n",
" _ = await client.GetAsync(global_default_page);\n",
" displayer.Update(($\"第{i+1}次请求完成\"));\n",
" \n",
" await Task.Delay(TimeSpan.FromSeconds(2));\n",
" }\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"使用自定义设置依次向同一端点发出5个请求。在每个请求之间暂停两秒钟。输出从DNS检索到的网站服务器的IPv4地址。我们可以使用此IP地址来查看通过PowerShell中发出的netstat命令对其打开的连接"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "pwsh"
},
"polyglot_notebook": {
"kernelName": "pwsh"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"# 若查询不到,则异常\n",
"#!set --value @csharp:global_netstat_filter --name queryFilter\n",
"\n",
"Write-Host \"请先执行上面的单元,再执行本单元\"\n",
"Write-Host \"网络状态\"\n",
"netstat -ano | findstr $queryFilter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在这种情况下到远程端点的连接只有1个。在每个请求之后该连接将返回到池中因此在发出下一个请求时可以重新使用。\n",
"如果更改连接的生存期以使它们在1秒后过期测试这对行为的影响"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"//程序池设置\n",
"{ \n",
" //自定义行为\n",
" Console.WriteLine(\"程序运行大约要10-20请在程序退出后执行下面命令行查看网络情况\");\n",
" var socketsHandler2 = new SocketsHttpHandler\n",
" {\n",
" PooledConnectionLifetime = TimeSpan.FromSeconds(1),\n",
" PooledConnectionIdleTimeout = TimeSpan.FromSeconds(1),\n",
" MaxConnectionsPerServer = 1\n",
" };\n",
"\n",
" var client2 = new HttpClient(socketsHandler2)\n",
" {\n",
" BaseAddress = new Uri(global_api_config.BaseUrl)\n",
" };\n",
"\n",
" var displayer = \"\".Display();\n",
"\n",
" for (var i = 0; i < 5; i++)\n",
" {\n",
" if(i>0)\n",
" {\n",
" await Task.Delay(TimeSpan.FromSeconds(2));\n",
" }\n",
" _ = await client2.GetAsync(global_default_page);\n",
" displayer.Update(($\"第{i+1}次请求完成\"));\n",
" \n",
" await Task.Delay(TimeSpan.FromSeconds(2));\n",
" }\n",
" \n",
" //调用命令行,显示查看网络情况\n",
" string command = $\"netstat -ano | findstr {global_netstat_filter}\";\n",
" \n",
" // 创建一个新的ProcessStartInfo对象\n",
" ProcessStartInfo startInfo = new ProcessStartInfo(\"cmd\", $\"/c {command}\")\n",
" {\n",
" RedirectStandardOutput = true, // 重定向标准输出\n",
" UseShellExecute = false, // 不使用系统外壳程序启动\n",
" CreateNoWindow = true // 不创建新窗口\n",
" };\n",
" \n",
" // 启动进程\n",
" using (Process process = Process.Start(startInfo))\n",
" {\n",
" // 读取cmd的输出\n",
" using (StreamReader reader = process.StandardOutput)\n",
" {\n",
" string stdoutLine = reader.ReadToEnd();\n",
" Console.WriteLine(stdoutLine);\n",
" }\n",
" }\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "pwsh"
},
"polyglot_notebook": {
"kernelName": "pwsh"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"#!set --value @csharp:global_netstat_filter --name queryFilter\n",
"netstat -ano | findstr $queryFilter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"在这种情况下我们可以看到使用了五个连接。其中的前四个在1秒后从池中删除因此无法在下一个请求中重复使用。结果每个请求都打开了一个新连接。现在原始连接处于TIME_WAIT状态并且操作系统无法将其重新用于新的出站连接。最终连接显示为ESTABLISHED因为我在它过期之前就抓住了它。"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### 测试最大连接数"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"/*\n",
"\t功能将MaxConnectionsPerServer限制为2。然后启动200个任务每个任务都向同一端点发出HTTP请求。这些任务将同时运行。所有请求竞争所花费的时间将写入控制台。\n",
"\t\t 随即调用用netstat命令查看连接则根据定义的限制我们可以看到两个已建立的连接。\n",
"*/\n",
"{\n",
"\tConsole.WriteLine(\"开始请求网络...\");\n",
"\tvar socketsHandler = new SocketsHttpHandler\n",
"\t{\n",
"\t\tPooledConnectionLifetime = TimeSpan.FromSeconds(60),\n",
"\t\tPooledConnectionIdleTimeout = TimeSpan.FromMinutes(20),\n",
"\t\tMaxConnectionsPerServer = 2\n",
"\t};\n",
"\n",
"\tvar client = new HttpClient(socketsHandler)\n",
"\t{\n",
"\t\tBaseAddress = new Uri(global_api_config.BaseUrl)\n",
"\t};\n",
"\n",
"\tvar sw = Stopwatch.StartNew();\n",
"\n",
"\tvar tasks = Enumerable.Range(0, 200).Select(i => client.GetAsync(global_default_page));\n",
"\n",
"\tawait Task.WhenAll(tasks);\n",
"\n",
"\tsw.Stop();\n",
"\t\t\n",
"\tConsole.WriteLine($\"共请求了200次耗时 {sw.ElapsedMilliseconds} 毫秒\");\n",
"\t\t\n",
"\t//执行查看网络状态方法\n",
"\tConsole.WriteLine(\"当前网络状态\");\n",
"\tvar message = HttpClientStudy.Core.Utilities.AppUtility.RunCmd($\"netstat -ano | findstr {global_netstat_filter}\");\n",
"\tConsole.WriteLine(message);\n",
"}"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "pwsh"
},
"polyglot_notebook": {
"kernelName": "pwsh"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"# 重新查询当前网络状态\n",
"#!set --value @csharp:global_netstat_filter --name queryFilter\n",
"netstat -ano | findstr $queryFilter"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"如果我们调整此代码以允许MaxConnectionsPerServer = 10则可以重新运行该应用程序。耗时将减少大约4倍。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"{ //MaxConnectionsPerServer 设置为10网络连接将增加到10个耗时将减少到1/4\n",
"\tConsole.WriteLine(\"开始请求网络...\");\n",
"\tvar socketsHandler = new SocketsHttpHandler\n",
"\t{\n",
"\t\tPooledConnectionLifetime = TimeSpan.FromSeconds(60),\n",
"\t\tPooledConnectionIdleTimeout = TimeSpan.FromMinutes(20),\n",
"\t\tMaxConnectionsPerServer = 10\n",
"\t};\n",
"\n",
"\tvar client = new HttpClient(socketsHandler)\n",
"\t{\n",
"\t\tBaseAddress = new Uri(global_api_config.BaseUrl)\n",
"\t};\n",
"\n",
"\t//client.Display();\n",
"\n",
"\tvar sw = Stopwatch.StartNew();\n",
"\n",
"\tvar tasks = Enumerable.Range(0, 200).Select(i => client.GetAsync(global_default_page));\n",
"\n",
"\tawait Task.WhenAll(tasks);\n",
"\n",
"\tsw.Stop();\n",
"\t\t\n",
"\tConsole.WriteLine($\"共请求了200次耗时 {sw.ElapsedMilliseconds} 毫秒\");\n",
"\t\t\n",
"\t//执行查看网络状态方法\n",
"\tConsole.WriteLine(\"当前网络状态\");\n",
"\tvar message = AppUtility.RunCmd($\"netstat -ano | findstr {global_netstat_filter}\");\n",
"\tConsole.WriteLine(message);\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3、推荐使用方式"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"总则:\n",
"\n",
" 一、 应使用长期客户端(静态对象、单例等),并设置 PooledConnectionLifetime。这能解决DNS问题和套接字耗尽问题。\n",
" \n",
" 二、 使用 IHttpClientFactory 创建的短期客户端:"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"+ 在 .NET Core 和 .NET 5+ 中:\n",
" \n",
" + 根据预期的 DNS 更改,使用 static 或 singletonHttpClient 实例,并将 PooledConnectionLifetime 设置为所需间隔(例如 2 分钟)。 这可以解决端口耗尽和 DNS 更改两个问题,而且不会增加 IHttpClientFactory 的开销。 如果需要模拟处理程序,可以单独注册它。\n",
" \n",
" + 使用 IHttpClientFactory可以针对不同的用例使用多个以不同方式配置的客户端。 但请注意,工厂创建的客户端生存期较短,一旦创建客户端,工厂就不再可以控制它。\n",
" 工厂合并 HttpMessageHandler 实例,如果其生存期尚未过期,则当工厂创建新的 HttpClient 实例时,可以从池中重用处理程序。 这种重用避免了任何套接字耗尽问题。\n",
" 如果需要 IHttpClientFactory 提供的可配置性,我们建议使用类型化客户端方法。\n",
"\n",
"+ 在 .NET Framework 中,使用 IHttpClientFactory 管理 HttpClient 实例。 如果不使用工厂,而是改为自行为每个请求创建新的客户端实例,则可能耗尽可用的端口。 \n",
"\n",
"`提示`: 如果应用需要 Cookie请考虑禁用自动 Cookie 处理或避免使用 IHttpClientFactory。 共用 HttpMessageHandler 实例会导致共享 CookieContainer 对象。 意外的 CookieContainer 对象共享通常会导致错误的代码。"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"{ //不推荐的示例\n",
" int requestCount =0;\n",
"\n",
" //这会建立10个 HttpClient \n",
" //尽管使用了Using不过Using只保证应用进程释放实例但是http请求是跨操作系统、跨网络的操作调用Using的进程管不了操作系统更管不了网络。\n",
" //如果把循环次数加大到 65535 就会一定导致夏套接字耗尽(2000以很可能就会出现)。\n",
" Parallel.For(0,10,async (a,b)=>\n",
" {\n",
" using (var client = new HttpClient())\n",
" {\n",
" _ = await client.GetAsync (global_api_config.BaseUrl + global_default_page);\n",
" } \n",
" Interlocked.Add(ref requestCount, 1);\n",
" });\n",
"}\n",
"\n",
"{ //使用长期客户端\n",
" using (var client = new HttpClient())\n",
" {\n",
" client.BaseAddress = new Uri(global_api_config.BaseUrl);\n",
" \n",
" for(int i=0; i<10; i++)\n",
" {\n",
" //n次调用均使用同一个 HttpClient 实例\n",
" _ = await client.GetAsync(global_default_page);\n",
" }\n",
" }// 所有调用完成,才释放 HttpClient 实例\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4、静态客户端的复原能力"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"dotnet_interactive": {
"language": "csharp"
},
"polyglot_notebook": {
"kernelName": "csharp"
},
"vscode": {
"languageId": "polyglot-notebook"
}
},
"outputs": [],
"source": [
"#r \"nuget:Polly\"\n",
"#r \"nuget:Microsoft.Extensions.Http.Resilience\"\n",
"using System;\n",
"using System.Net.Http;\n",
"using Microsoft.Extensions.Http;\n",
"using Microsoft.Extensions.Http.Resilience;\n",
"using Polly;\n",
"\n",
"{\n",
" var retryPipeline = new ResiliencePipelineBuilder<HttpResponseMessage>()\n",
" .AddRetry(new HttpRetryStrategyOptions\n",
" {\n",
" BackoffType = DelayBackoffType.Exponential,\n",
" MaxRetryAttempts = 3\n",
" })\n",
" .Build();\n",
"\n",
" var socketHandler = new SocketsHttpHandler\n",
" {\n",
" PooledConnectionLifetime = TimeSpan.FromMinutes(15)\n",
" };\n",
"\n",
" #pragma warning disable EXTEXP0001\n",
" var resilienceHandler = new ResilienceHandler(retryPipeline)\n",
" {\n",
" InnerHandler = socketHandler,\n",
" };\n",
" #pragma warning restore EXTEXP0001\n",
"\n",
" var httpClient = new HttpClient(resilienceHandler);\n",
" httpClient.BaseAddress = new Uri(global_api_config.BaseUrl);\n",
"\n",
" var response = await httpClient.GetAsync(global_default_page);\n",
" var htmlText = await response.Content.ReadAsStringAsync();\n",
" Console.WriteLine($\"共有{htmlText.Length}个字符\");\n",
"}\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": ".NET (C#)",
"language": "C#",
"name": ".net-csharp"
},
"language_info": {
"name": "python"
},
"polyglot_notebook": {
"kernelInfo": {
"defaultKernelName": "csharp",
"items": [
{
"aliases": [],
"name": "csharp"
}
]
}
}
},
"nbformat": 4,
"nbformat_minor": 2
}