去年9月份的时候我看到过外国朋友关于.NET Framework下HttpClient缺陷的分析后对HttpClient有了必定的了解。前几日也有园友写了一篇关于HttpClient的分析文章, 因而我想深刻探索一下在.NET下使用HTTP请求的正确姿式。姿式不是越多越好, 而在于精不精。若是不深刻了解, 小朋友可能会这样想: 啊, 这个姿式不High, 那我换一个吧, 却不知那一个姿式也有问题啊, 亲。html
中文版: https://oschina.net/news/77036/httpclientgit
英文版: https://www.infoq.com/news/2016/09/HttpClient程序员
张大大版: http://www.cnblogs.com/lori/p/7692152.htmlgithub
2、准备好床和各类姿式web
1. 研究姿式必然是要先准备好支撑点, 做为一个传统的人, 仍是比较喜欢床。多线程
.NET Framework, .NET CORE Windows, .NET CORE Linux, .NET CORE Maccurl
2. 姿式有如下几种, 若是小朋友们有各特别的能够告诉我呀, 我很乐于尝试的。post
HttpClient, WebClient, HttpWebRequest性能
3、让咱们大干一场吧测试
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(); } }
.NET Framework | .NET Core Windows | .NET Core Linux | .NET Core Mac | |
HttpWebRequest | 2 | 端口占用数迅速攀升到1000+ | 性能不好, 端口占用数攀升到70+并稳定 |
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(); }
.NET Framework | .NET Core Windows | .NET Core Linux | .NET Core Mac | |
WebClient | 2 | 端口占用数迅速攀升到1000+ | 性能较差, 端口占用数攀升到400+稳定 |
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(); }
.NET Framework | .NET Core Windows | .NET Core Linux | .NET Core Mac | |
WebClient | 2 | 端口占用数迅速攀升到1000+ | 端口占用数迅速攀升到1000+ |
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(); }
.NET Framework | .NET Core Windows | .NET Core Linux | .NET Core Mac | |
HttpClient | 10 | 10 | 10 |
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(); }
.NET Framework | .NET Core Windows | .NET Core Linux | .NET Core Mac | |
HttpClient | 端口占用数迅速攀升到1000+ | 端口占用数迅速攀升到1000+ | 性能较差, 端口占用数攀升到200+ |
结论
.NET Framework | .NET Core Windows | .NET Core Linux | .NET Core Mac | |
HttpWebRequest | OK | Abnormal | Abnormal | |
WebClient | OK | Abnormal | Abnormal | |
HttpClient(每一个线程一个对象) | OK | OK | OK | |
HttpClient(using) | Abnormal | Abnormal | Abnormal |
有意思的细节与疑问
1. WebClient和HttpWebRequest为何在10个线程下端口数为2而且都为2
2. Linux下并行性能明显变差
4、追根溯源
下载.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://"); var response = webRequest.GetResponse(); response.Dispose(); Console.WriteLine($"Process: {i}."); Thread.Sleep(5); } }); Console.Read(); }
.NET Framework | .NET Core Windows | .NET core Linux | .NET Core Mac | |
HttpWebRequest | 10 | 端口占用迅速攀升到1000+ | 性能不好, 端口占用攀升到70+并稳定 |
你们看.NET Core下虽然能够设置 ServicePointManager.DefaultConnectionLimit = 10; 可是依然没什么卵用... 缘由也很明显, HttpWebRequest根本没有使用ServicePointManager作管理。在我查了源码后虽然.NET Core实现了ServicePointManager和ServicePoint, 不过已经迁到另一个项目下面, 也未发现有什么做用。因此你们千万要注意,不要觉得在.NET Core能够设置ServicePointManager.DefaultConnectionLimit这个值了, 就觉得.NET Framework下的效果会一致( 其它地方同理)
5. HttpClient在.NET Framework下的代码我没有找到, ILSpy也查看不了, 但猜测应该是和.NET Core下一致的, 因此才会有同样的表象, 有大神知道的能够告诉我一下。
5、好累啊, 终于交差了, 就是不知道知足不知足
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, 这是后话了
6、抽一根烟吧
1. 大胆猜测一下, 微软应该是赶进度才偷懒使用HttpClient来实现HttpWebRequest致使的吧。
2. Linux并行性能好像差不少, 缘由不明, 请听下回分解
3. 这也就是开源的魅力所在了吧! 咱们能够顺藤摸瓜, 查明真相。让咱们一块儿为.NET Core的开源事业奉献本身的一份力吧(其实我只是不想丢饭碗好吧:::)
4. 若是有说错请指正, 不接受漫骂
5. 欢迎各路大神和做品加入 https://github.com/dotnetcore (中国 .net core 开源小分队)
6. 月收入低于3万的也是程序员!!!!
============================================================================================================================================================
接上回咱们留下了一个性能疑问, Linux下性能明显有问题。为了映证我所看到的,作以下测试
static void Main(string[] args) { Stopwatch watch = new Stopwatch(); HttpClient client = new HttpClient(); watch.Start(); for (int i = 0; i < 50; ++i) { var html = client.GetStringAsync("http://").Result; Console.WriteLine($"Process: {i}."); } watch.Stop(); Console.WriteLine($"Cost: {watch.ElapsedMilliseconds}"); Console.Read(); }
.NET Framework | .NET Core Windows | .NET core Linux | ||
HttpClient | 400+ (屡次) | 450+(屡次) | 1230+(屡次), 5333+屡次, 350+(屡次)很是不稳定 |
Linux下性能的波动彻底不可理解..... 因而调整测试代码
static void Main(string[] args) { Stopwatch watch = new Stopwatch(); HttpClient client = new HttpClient(new HttpClientHandler()); watch.Start(); for (int i = 0; i < 50; ++i) { Stopwatch watch1 = new Stopwatch(); watch1.Start(); var html = client.GetStringAsync("http://").Result; Console.WriteLine($"Process: {i}."); watch1.Stop(); Console.WriteLine($"Cost: {watch1.ElapsedMilliseconds}"); } watch.Stop(); Console.WriteLine($"Cost: {watch.ElapsedMilliseconds}"); Console.Read(); }
测试结果一目了然... 全是第一次请求消耗的时间, 后面的请求基本都是6左右, 仍是很是快的。