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

六、抽一根烟吧

1. 大胆猜想一下, 微软应该是赶进度才偷懒使用HttpClient来实现HttpWebRequest导致的吧。

2. Linux并行性能好像差很多, 原因不明, 请听下回分解

3. 这也就是开源的魅力所在了吧! 我们可以顺藤摸瓜, 查明真相。让我们一起为.NET Core的开源事业奉献自己的一份力吧(其实我只是不想丢饭碗好吧:::)

4. 如果有说错请指正, 不接受漫骂

5. 欢迎各路大神和作品加入 https://github.com/dotnetcore (中国 .net core 开源小分队)

6. 月收入低于3万的也是程序员!!!!

.NET下使用HTTP请求的正确姿势的更多相关文章

  1. 程序员节应该写博客之.NET下使用HTTP请求的正确姿势

    程序员节应该写博客之.NET下使用HTTP请求的正确姿势 一.前言 去年9月份的时候我看到过外国朋友关于.NET Framework下HttpClient缺陷的分析后对HttpClient有了一定的了 ...

  2. 【分布式缓存系列】集群环境下Redis分布式锁的正确姿势

    一.前言 在上一篇文章中,已经介绍了基于Redis实现分布式锁的正确姿势,但是上篇文章存在一定的缺陷——它加锁只作用在一个Redis节点上,如果通过sentinel保证高可用,如果master节点由于 ...

  3. Win10下注册APlayer组件的正确姿势

    1. 官网下载SDK 和 解码器 APlayer媒体播放引擎 2.解压SDK和解码器,把解码器codecs文件夹内所有文件复制到SDK文件夹内的bin\codecs目录里面 3.使用管理员权限打开CM ...

  4. 读取ClassPath下resource文件的正确姿势

    1.前言 为什么要写这篇文章?身为Java程序员你有没有过每次需要读取 ClassPath 下的资源文件的时候,都要去百度一下,然后看到下面的这种答案: Thread.currentThread(). ...

  5. windows系统下npm升级的正确姿势以及原理

    本文来自网易云社区 作者:陈观喜 网上关于npm升级很多方法多种多样,但是在windows系统下不是每种方法都会正确升级.其中在windows系统下主要的升级方法有以下三种: 首先最暴力的方法删掉no ...

  6. Redis实现分布式锁的正确姿势

    分布式锁一般有三种实现方式:1. 数据库乐观锁:2. 基于Redis的分布式锁:3. 基于ZooKeeper的分布式锁.本篇博客将介绍第二种方式,基于Redis实现分布式锁.虽然网上已经有各种介绍Re ...

  7. Golang错误和异常处理的正确姿势

    Golang错误和异常处理的正确姿势 错误和异常是两个不同的概念,非常容易混淆.很多程序员习惯将一切非正常情况都看做错误,而不区分错误和异常,即使程序中可能有异常抛出,也将异常及时捕获并转换成错误.从 ...

  8. Android PermissionUtils:运行时权限工具类及申请权限的正确姿势

    Android PermissionUtils:运行时权限工具类及申请权限的正确姿势 ifadai 关注 2017.06.16 16:22* 字数 318 阅读 3637评论 1喜欢 6 Permis ...

  9. Redis全方位详解--数据类型使用场景和redis分布式锁的正确姿势

    一.Redis数据类型 1.string string是Redis的最基本数据类型,一个key对应一个value,每个value最大可存储512M.string一半用来存图片或者序列化的数据. 2.h ...

随机推荐

  1. 金蝶BOS元模型分析

    对一些需求变化多样的产品而言,做好可变性设计是非常重要的.国外做得好的有Siebel,国内有金蝶的BOS,实际上金蝶的BOS很多理念跟Siebel是相似的,呵呵...他们都是采用MDD的方式来解决可变 ...

  2. Qt中QWidget、QDialog和QMainWindow

    QWidget 类是所有用户界面对象的基类.只有一个"页面" QMainWindow 是一个"窗口".含有菜单栏.状态栏.工具栏.停靠窗口.中心窗口 QDial ...

  3. 用jTessBoxEditorFX训练字库

    软件下载:https://sourceforge.net/projects/vietocr/files/jTessBoxEditor/ 官方字库下载:https://github.com/tesser ...

  4. javascript中事件总结&通用的事件侦听器函数封装&事件委托

    前言: JAVASCRIPT与HTML之间的交互是通过事件来实现的.事件,就是文档或浏览器窗口中发生的一些特定交互瞬间.可以使用侦听器( 或处理程序 )来预定事件,以便事件发生时执行相应的代码.这种在 ...

  5. unity客户端自动断开连接

    问题描述: 当unity失去焦点一段时间后,与photon服务器端断开连接 问题根源: 默认unity失去焦点后会暂停运行,导致连接超时,服务器端超时断开连接 解决方案: 连接服务器后,填加一句代码让 ...

  6. 前端页面模拟浏览器搜索功能Ctrl+F实现

    <html> <head> <style type="text/css"> .res { color: Red; } .result{ back ...

  7. JDOJ 1133 分段公司利润

    JDOJ 1133: 分段公司利润 JDOJ传送门 Description 企业发放的奖金根据利润提成.利润低于或等于100000元的,奖金可提10%; 利润高于100000元,低于200000元(1 ...

  8. selenium 中在 iframe 内的元素定位

    有些时候 元素明明就在 但是通过什么方式定位都提示 定位不到元素 此时就要考虑元素是不是内嵌在iframe 中 对于内嵌在 ifra中的元素定位 首先定位到 iframe 元素 例如 iframe = ...

  9. Excel 使用小技巧

    连接符,符号是&,输入方式为按住Shift+7“&”在连接单元格内容时,不管单元格格式为文本还是数值,都能连接,但得到的结果都是文本型数字. ="update tb_reco ...

  10. zy的日志报表执行有问题。crontab显示执行了任务,代码中应该有问题

    crontab定时任务在日志记录中是执行了 但是在执行脚本的过程中报错了, 找不到问题原因,以后也要在脚本中加入日志记录, 但是奇怪的是在中午和晚上是正常的, 应该是那个时间段的判断逻辑有问题,导致程 ...