.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.pswp.cn/diannao/82504.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/82504.shtml
英文地址,請注明出處:http://en.pswp.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;在當前技術生態圈如此完善的情況下&#…