From 4988cbecc6e9f15da195231055830116232aae5b Mon Sep 17 00:00:00 2001 From: wanggaofeng <15601716045@163.com> Date: Tue, 28 May 2024 20:49:09 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Docs/1.2.使用准则.ipynb | 165 ++++++++++++++++++++++++++++++++++-- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/Docs/1.2.使用准则.ipynb b/Docs/1.2.使用准则.ipynb index a0a6b58..04ae77a 100644 --- a/Docs/1.2.使用准则.ipynb +++ b/Docs/1.2.使用准则.ipynb @@ -28,6 +28,12 @@ "cell_type": "code", "execution_count": null, "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, "vscode": { "languageId": "polyglot-notebook" } @@ -43,16 +49,18 @@ "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", "\n", - "var global_queryDomain=\"soft.pwidc.cn\";\n", - "var global_queryPort=80;\n", + "var global_queryDomain = \"soft.pwidc.cn\";\n", + "var global_queryPort = 80;\n", "var global_queryBaseUrl = $\"http://{global_queryDomain}:{global_queryPort}\";\n", "\n", - "var global_ips =Dns.GetHostAddresses(global_queryDomani);\n", - "var global_queryIp = ips.First().ToString();\n", - "var global_netstat_filter = $\"{queryIp}:{global_queryPort}\";" + "var global_ips = Dns.GetHostAddresses(global_queryDomain);\n", + "var global_queryIp = global_ips.First().ToString();\n", + "var global_netstat_filter = $\"{global_queryIp}:{global_queryPort}\";" ] }, { @@ -66,6 +74,12 @@ "cell_type": "code", "execution_count": null, "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, "vscode": { "languageId": "polyglot-notebook" } @@ -557,6 +571,12 @@ "cell_type": "code", "execution_count": null, "metadata": { + "dotnet_interactive": { + "language": "csharp" + }, + "polyglot_notebook": { + "kernelName": "csharp" + }, "vscode": { "languageId": "polyglot-notebook" } @@ -597,6 +617,141 @@ "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_queryBaseUrl);\n", + " } \n", + " Interlocked.Add(ref requestCount, 1);\n", + " });\n", + "}\n", + "\n", + "{ //使用长期客户端\n", + " using (var client = new HttpClient())\n", + " {\n", + " for(int i=0; i<10; i++)\n", + " {\n", + " //n次调用,均使用同一个 HttpClient 实例\n", + " _ = await client.GetAsync(global_queryBaseUrl);\n", + " }\n", + " }// 所有调用完成,才释放 HttpClient 实例\n", + "}" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4、静态客户端的复原能力" + ] + }, + { + "cell_type": "code", + "execution_count": 62, + "metadata": { + "vscode": { + "languageId": "polyglot-notebook" + } + }, + "outputs": [ + { + "ename": "Error", + "evalue": "Command cancelled.", + "output_type": "error", + "traceback": [ + "Command cancelled." + ] + } + ], + "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()\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", + "\n", + " var response = await httpClient.GetAsync(\"https://www.baidu.com\");\n", + " var htmlText = await response.Content.ReadAsStringAsync();\n", + " Console.WriteLine($\"共有{htmlText.Length}个字符\");\n", + "}\n" + ] } ], "metadata": {