.NET下使用HTTP請求的正確姿勢


來源:Lewis.Zou

cnblogs.com/modestmt/p/7724821.html

 

一、前言

 

去年9月份的時候我看到過外國朋友關於.NET Framework下HttpClient缺陷的分析后對HttpClient有了一定的了解。前幾日也有園友寫了一篇關於HttpClient的分析文章, 於是我想深入探索一下在.NET下使用HTTP請求的正確姿勢。姿勢不是越多越好, 而在於精不精。如果不深入了解, 小朋友可能會這樣想: 啊, 這個姿勢不High, 那我換一個吧, 殊不知那一個姿勢也有問題啊, 親。

 

中文版: https://oschina.net/news/77036/httpclient

 

英文版: https://www.infoq.com/news/2016/09/HttpClient

 

張大大版: http://www.cnblogs.com/lori/p/7692152.html

 

二、准備好床和各種姿勢

 

1. 研究姿勢必然是要先准備好支撐點, 作為一個傳統的人, 還是比較喜歡床。

 

.NET Framework, .NET CORE Windows, .NET CORE Linux, .NET CORE Mac

 

2. 姿勢有以下幾種, 如果小朋友們有各特別的可以告訴我呀, 我很樂於嘗試的。

 

HttpClient, WebClient, HttpWebRequest

 

三、讓我們大干一場

 

Windows下統計端口使用的命令: netstat -ano | find "{port}" /c 

 

Linux 下統計端口使用的命令:  netstat -nat|grep -i "{port}"|wc -l

 

HttpWebRequest 測試代碼如下

 

class Program

{

    static void Main(string[] args)

    {

        Parallel.For(0, 10, (i) =>

        {

            while (true)

            {

                var webRequest = (HttpWebRequest)WebRequest.CreateHttp("http://");

                var response = webRequest.GetResponse();

                response.Dispose();

                Console.WriteLine($"Process: {i}.");

                Thread.Sleep(5);

            }

        });

        Console.Read();

    }

}

 

 

 

WebClient因為有IDisposable接口, 於是我做兩份測試

 

static void Main(string[] args)

{

    Parallel.For(0, 10, (i) =>

    {

        while (true)

        {

            using (WebClient client = new WebClient())

            {

                client.DownloadString("http://");

                Console.WriteLine($"Process: {i}.");

            }

            Thread.Sleep(5);

        }

    });

    Console.Read();

}

 

 

 

 

static void Main(string[] args)

{

    Parallel.For(0, 10, (i) =>

    {

        WebClient client = new WebClient();

        while (true)

        {

            client.DownloadString("http://");

            Console.WriteLine($"Process: {i}.");

            Thread.Sleep(5);

        }

    });

    Console.Read();

}

 

 

HttpClient有IDisposable接口, 也做兩份測試

 

static void Main(string[] args)

{

    Parallel.For(0, 10, (i) =>

    {

        HttpClient client = new HttpClient();

        while (true)

        {

            var html = client.GetStringAsync("http://").Result;

            Console.WriteLine($"Process: {i}.");

            Thread.Sleep(5);

        }

    });

    Console.Read();

}

 

 

static void Main(string[] args)

{

    Parallel.For(0, 10, (i) =>

    {

        while (true)

        {

            using (HttpClient client = new HttpClient())

            {

                var html = client.GetStringAsync("http://").Result;

                Console.WriteLine($"Process: {i}.");

            }

            Thread.Sleep(5);

        }

    });

    Console.Read();

}

 

 

 

有意思的細節與疑問

 

1. WebClient和HttpWebRequest為什么在10個線程下端口數為2並且都為2

 

2. Linux下並行性能明顯變差

 

四、追根溯源

 

下載.net45源碼和corefx源碼

 

http://referencesource.microsoft.com/ 右上角Download

 

https://github.com/dotnet/corefx

 

1. 分析.NET Core下WebClient的代碼, 發現它是使用WebRequest即HttpWebRequest來請求數據

 

 

2. 分析.NET Core下HttpWebRequest的代碼找到SendRequest方法

 

 

熟悉嗎?!!原來.NET Core一切的根源都出在HttpClient身上...

 

3. 順着HttpClient代碼我們可以發現, 微軟為Windows, Unix各自實現了WinHttpHandler和CurlHandler, 猜測Uniux下使用的是Curl. 最終確實能查到Windows下是DLLImport了winhttp.dll, 但Unix系統是DLLImport的 System.Net.Http.Native, 這是個什么我暫時不清楚, 也不清楚它跟curl的關系, 也許是一個中轉調用。

 

 

4. 我們再回過頭來看.NET Framework下為什么HttpWebRequest和WebClient是正常的, WebClient依然是使用的HttpWebRequest, 因此推斷.NET Framework的HttpWebRequest的實現與.NET Core是不一致的。

 

簡單的查找代碼, 果然每一個Http請求是由ServicePointManager管理的ServicePoint來實現的, 並且ServicePoint是使用.NET下Socket來實現的, 一切就明了了。現在對剛才說的 “WebClient和HttpWebRequest為什么在10個線程下端口數為2並且都為2”有感覺了吧?我們把剛才的測試代碼再加上一行

 

static void Main(string[] args)

{

    ServicePointManager.DefaultConnectionLimit = 10;

    Parallel.For(0, 10, (i) =>

    {

        while (true)

        {

            var webRequest = (HttpWebRequest)WebRequest.CreateHttp("http://www.ooodata.com:5000");

            var response = webRequest.GetResponse();

            response.Dispose();

            Console.WriteLine($"Process: {i}.");

            Thread.Sleep(5);

        }

    });

    Console.Read();

}

 

 

 

大家看.NET Core下雖然可以設置 ServicePointManager.DefaultConnectionLimit = 10; 但是依然沒什么卵用...  原因也很明顯, HttpWebRequest根本沒有使用ServicePointManager做管理。

 

在我查了源碼后雖然.NET Core實現了ServicePointManager和ServicePoint, 不過已經遷到另外一個項目下面, 也未發現有什么作用。所以大家千萬要注意,不要以為在.NET Core可以設置ServicePointManager.DefaultConnectionLimit這個值了, 就以為.NET Framework下的效果會一致( 其它地方同理)

 

5. HttpClient在.NET Framework下的代碼我沒有找到, ILSpy也查看不了, 但猜想應該是和.NET Core下一致的, 所以才會有一樣的表象, 有大神知道的可以告訴我一下。

 

五、好累啊, 終於交差了, 就是不知道滿足不滿足

 

1. 在.NET Framework下盡量使用HttpWebRequest或者WebClient, 並且根據你自己的多線程情況設置 ServicePointManager.DefaultConnectionLimit的值, 以及ThreadPool.SetMinThreads(200, 200)的值

 

2. 在.NET Framework下如果一定要使用HttpClient, 則應該一個線程使用一個HttpClient對象, 這樣不會出現端口被耗盡的情況

 

3. 在.NET Core 2.0下只有HttpClient一條路選, 並且一個線程使用一個HttpClient對象, 當然也許我們可以參照.NET Framework下的代碼重新實現一個ServicePointManager管理的HttpWebRequest, 這是后話了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM