一、前言

去年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(, , (i) =>
{
while (true)
{
var webRequest = (HttpWebRequest)WebRequest.CreateHttp("http://");
var response = webRequest.GetResponse();
response.Dispose();
Console.WriteLine($"Process: {i}.");
Thread.Sleep();
}
});
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(, , (i) =>
{
while (true)
{
using (WebClient client = new WebClient())
{
client.DownloadString("http://");
Console.WriteLine($"Process: {i}.");
}
Thread.Sleep();
}
});
Console.Read();
}
  .NET Framework .NET Core Windows  .NET Core Linux .NET Core Mac
WebClient 2 端口占用数迅速攀升到1000+ 性能较差, 端口占用数攀升到400+稳定  
        static void Main(string[] args)
{
Parallel.For(, , (i) =>
{
WebClient client = new WebClient();
while (true)
{
client.DownloadString("http://");
Console.WriteLine($"Process: {i}."); Thread.Sleep();
}
});
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(, , (i) =>
{
HttpClient client = new HttpClient();
while (true)
{
var html = client.GetStringAsync("http://").Result;
Console.WriteLine($"Process: {i}."); Thread.Sleep();
}
});
Console.Read();
}
  .NET Framework .NET Core Windows .NET Core Linux .NET Core Mac
HttpClient 10 10 10  
        static void Main(string[] args)
{
Parallel.For(, , (i) =>
{
while (true)
{
using (HttpClient client = new HttpClient())
{
var html = client.GetStringAsync("http://").Result;
Console.WriteLine($"Process: {i}.");
}
Thread.Sleep();
}
});
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下并行性能明显变差

四、追根溯源

下载.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 = ;
Parallel.For(, , (i) =>
{
while (true)
{
var webRequest = (HttpWebRequest)WebRequest.CreateHttp("http://");
var response = webRequest.GetResponse();
response.Dispose();
Console.WriteLine($"Process: {i}.");
Thread.Sleep();
}
});
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下一致的, 所以才会有一样的表象, 有大神知道的可以告诉我一下。

五、好累啊, 终于交差了, 就是不知道满足不满足

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, 这是后话了

六、抽一根烟吧

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 = ; i < ; ++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 = ; i < ; ++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左右, 还是非常快的。

程序员节应该写博客之.NET下使用HTTP请求的正确姿势的更多相关文章

  1. .NET下使用HTTP请求的正确姿势

    来源:Lewis.Zou cnblogs.com/modestmt/p/7724821.html 一.前言 去年9月份的时候我看到过外国朋友关于.NET Framework下HttpClient缺陷的 ...

  2. 150922-写写博客监督下不自觉的自己-PPT,Linux,HTML

    开始学PHP的日子里,总是懒散的有一天没一天的.无意间听闻写博客来展示代码(现在还远远做不到哇),来监督个人每天的学习进度,鉴于自己还是爱写一点文字,但愿可以坚持下去. 凡是都喜欢有个计划,骨子里的理 ...

  3. (转)[BetterExplained]为什么你应该(从现在开始就)写博客

    (一)为什么你应该(从现在开始就)写博客 用一句话来说就是,写一个博客有很多好处,却没有任何明显的坏处.(阿灵顿的情况属于例外,而非常态,就像不能拿抽烟活到一百岁的英国老太太的个例来反驳抽烟对健康的极 ...

  4. 如果简单的记录,就可以为这个世界创造更多的财富,那么还有什么理由不去写博客呢? — 读<<黑客与画家>> 有感

    上一次博文发文时间是2016.1.15,7个月已经过去了.最近读了一本<>的书,对我触动挺大的!里面有关于技术趋势的探讨,也有关于人生和财富的思考! 开始更新iOS122的文章的初衷是,聚 ...

  5. 好久没有写博客了,发现Live Writer也更新了

    最近由于工作变动,工作内容和心态也有所变化,所以很久没有写博客了,而且我的开源项目深蓝词库转换也很近没有更新了.今天打开LiveWriter发现居然有新版本,于是果断更新.现在新的LiveWriter ...

  6. 第一次尝试用 Live Writer 写博客

    之前在官网上下载了最新版的Windows Live Writer,可是安装不了,就在其他网站下了一个试试,可以安装,不过却是2009年的版本,很不喜欢,我希望能体验最新版的,回头还得重新下个最新版的安 ...

  7. 在github上写博客

    在github上混了几个月,收获颇多.作为一个开源的坚定信仰者,深深觉得每一个码农都应该参与到开源社区中,github提供了一个平台,让你为开源项目提交代码变得异常简单和直接.以前由于工作异常繁忙和繁 ...

  8. 用Jekyll在github上写博客——《搭建一个免费的,无限流量的Blog》的注脚

    本来打算买域名,买空间,用wordpress写博客的.后来问了一个师兄,他说他是用github的空间,用Jekyll写博客,说很多人都这么做.于是我就研究了一下. 比较有价值的文章有这么几篇: htt ...

  9. Orchard用LiveWriter写博客

    本文链接:http://www.cnblogs.com/souther/p/4544241.html Orchard本身提供一个内建的管理面板来写博客,许多人更喜欢采用客户端提交的方式,例如Windo ...

随机推荐

  1. 201521123015 《Java程序设计》第2周学习总结

    1.本章学习总结 (1)学习了枚举,数组等方法 (2)通过实验内容的讲解,解决了一些问题 (3)进一步运用和了解码云 书面作业 1.使用Eclipse关联jdk源代码,并查看String对象的源代码( ...

  2. 201521123072《java程序设计》第十三周学习总结

    201521123072<java程序设计>第十三周学习总结 1. 本周学习总结 以你喜欢的方式(思维导图.OneNote或其他)归纳总结多网络相关内容. 2. 书面作业 1. 网络基础 ...

  3. Java课程设计博客(团队)

    Java课程设计博客(团队) 1. 团队/项目名称 使用JAVA实现简易HTTP服务器 2. 团队成员 组长:林一心 组员:张杭镖 3. 项目git地址 https://github.com/oran ...

  4. 201521123051《Java程序设计》第十二周学习总结

    1. 本周学习总结 1.1 以你喜欢的方式(思维导图或其他)归纳总结多流与文件相关内容. 一 JAVA语言中主要通过流来完成IO操作. 流:计算机的输入输出之间流动的数据序列,也是类的对象.java中 ...

  5. 201521123113《Java程序设计》第13周学习总结

    1. 本周学习总结 2. 书面作业 Q1. 网络基础 1.1 比较ping www.baidu.com与ping cec.jmu.edu.cn,分析返回结果有何不同?为什么会有这样的不同? 返回的结果 ...

  6. Eclipse rap 富客户端开发总结(13) :Rap/Rcp保存按钮处理方式

    一.概述 在做项目的过程中,处理编辑区的保存机制的时候.发现,同样是扩展eclipse 自带的保存和全部保存按钮时候,rcp 工程下,保存按钮可以正常的灰显和可用,但是rap 的按钮就是始终呈现灰显的 ...

  7. Excel表科学记数法的数字和文本的转换

    一,科学记数法的数字转换文本类型: 1,还未有数据,先选中列或者单元格 右键单击->设置单元格格式->文本->确定 2,已有数据,先选中列或者单元格 右键单击->设置单元格格式 ...

  8. windows 结束进程的详细过程

    windows上如何结束进程的详细过程,下面附详细,图文说明 在cmd下,输入  netstat   -ano|findstr  8080      //说明:查看占用8080端口的进程 在cmd下, ...

  9. 协议端口号(protocol port number)

    协议端口号(protocol port number) 先来个注意事项 (-> ->) 这种在协议层间的抽象的协议端口是软件端口,和硬件端口是完全不同的概念.硬件端口是不同设备进行交互的接 ...

  10. Struts2的核心运行流程,原理图解

    感觉很有必要制定一个计划,这样盲目的想到哪里写到哪里,不符合我大程序员的思维逻辑呀~~~嗯...那就从基本的开始吧,循循渐进,今天想到的先写上,不能浪费了,哈哈哈................... ...