C#之使用HttpClient的优解

转载http://www.sohu.com/a/123674135_467799

新工作入职不满半周,目前仍然还在交接工作,适应环境当中,笔者不得不说看别人的源码实在是令人痛苦。所幸今天终于将大部分工作流畅地看了一遍,接下来就是熟悉框架技术的阶段了。

也正是在看源码的过程当中,有一个比较明显的用法细节引起了我的注意,我发现一位同事在请求远程Web Api时,虽然使用了 HttpClient 类,但是在用法上似乎有些欠考虑。代码抽象出来就是以下的模样:

using( varclient = newHttpClient()){ //do something}

我们知道 using 关键字常常和实现了 IDisposable 接口的类型一起使用(如数据库连接和文件流操作),用于释放对象机资源(关于GC回收的相关知识可参考我的另一篇博文),但是对于 HttpClient 这样直接和TCP/IP协议打交道的类型却是未必( HttpClient 继承了 HttpMessageInvoker 类, HttpMessageInvoker 实现了 IDisposable 接口,实现上是比较经典的代理模式),翻看一些国内外的文章都能看到对在 using 关键字中使用 HttpClient 的吐槽。事实是不是真的这样呢,其实只要做一个小实验就可以了。

让我们先试着运行以下代码

classProgram{ staticvoidMain(string[] args) { for( inti = 0; i < 10000; i++) { using( varclient = newHttpClient()) { varresult = client.GetAsync( “http://www.baidu.com”).Result; Console.WriteLine(result.StatusCode); } } Console.ReadKey(); } }

不出意外就会提示以下错误:

单纯为了解决问题而言,我们可以通过减小 HttpClient 的 Timeout 属性加快回收速度(修改系统变量可能会引发其他的问题),但实际上,这还是因为 HttpClient 消耗了太多套接字连接的关系。为了验证这个问题,我们可以使用TcpView这个小工具来查看下项目运行时的 TCP 连接数,如果你下载了代码运行后,会发现 TCP 连接和疯狗一样向上猛蹿。虽然还会有套接字回收的现象,但是和增加的速度相比确实是杯水车薪。

所以这时候我们需要换一种写法:

classProgram{ privatestaticreadonlyHttpClient Client= newHttpClient(); staticvoidMain(string[] args) { for( inti = 0; i < 10000; i++) { varresult = Client.GetAsync( “http://www.baidu.com”).Result; Console.WriteLine(result.StatusCode); } Console.ReadKey(); } }

更换以上写法后,我们会发现无论我们将循环上限如何调整,也不会出现套接字连接资源不足的情况了,而TCPView的结果也好看得多,甚至如果我们每次都测试传输时间的话,我们会发现单次调用 HttpClient 而言,第二种代码比第一种代码要快得多。其实这很好理解,HttpClient内部维持一个专有的连接池,每个HttpClient实例的请求相互隔绝,加快速度的原因是因为重用了套接字,去除了套接字重新建立连接的过程。这也很好地解释了dudu园长的那一篇博客 《》中的“预热”说法。盗一张图来说明一下套接字的使用情况。

因此,在使用 HttpClient 时我们知道以下几件小事

将其定义为单例模式(由单独的HttpClient维护连接池)

不要使用using关键字包裹(无效,套接字资源不会跟随释放)

尽量不要额外改变 HttpClient 的一些特殊行为(如上文中的TimeOut)

其实HttpClient还有一种使用隐患,DNS-Bug,这种做法国外也有同僚给出了相应的解释和解决方案,详情请见《》

单例模式扩展开来也有很多的说法,根据C#的一些规范,在编程中我推荐两种做法

A. 静态构造器

这种方式适用于如上代码场景,使用静态构造器确保静态字段的实例化。

classProgram{ privatestaticreadonlyHttpClient Client; staticProgram() { Client= newHttpClient(); } staticvoidMain(string[] args) { //do something} } B. HttpClientHelper

单例模式中,经典的双重检查锁定机制。

publicstaticclassHttpClientHelper{ privatestaticreadonlyobjectLockObj = newobject(); privatestaticHttpClient _client; publicstaticHttpClient HttpClient { get{ if(_client == null) { lock(LockObj) { if(_client == null) { _client= newHttpClient(); } } } return_client; } } } 寄语

多点实践多点总结,为认识更深刻的代码世界而奋斗。

C#使用 `HttpClient` 调用 WebService 接口的方法如下: 1. 首先需要添加 `System.Net.Http` 和 `System.Web.Services` 引用。 2. 在代码中创建 `HttpClient` 实例,并设置请求头信息和请求内容。 ```csharp using System; using System.Net.Http; using System.Threading.Tasks; using System.Xml; namespace ConsoleApp1 { class Program { static async Task Main(string[] args) { // 创建 HttpClient 实例 HttpClient httpClient = new HttpClient(); // 设置请求头信息 httpClient.DefaultRequestHeaders.Add("SOAPAction", "http://tempuri.org/HelloWorld"); // 设置请求内容 string soapRequest = @"<?xml version=""1.0"" encoding=""utf-8""?> <soap:Envelope xmlns:soap=""http://schemas.xmlsoap.org/soap/envelope/""> <soap:Body> <HelloWorld xmlns=""http://tempuri.org/"" /> </soap:Body> </soap:Envelope>"; HttpContent httpContent = new StringContent(soapRequest, System.Text.Encoding.UTF8, "text/xml"); // 发送请求 HttpResponseMessage httpResponse = await httpClient.PostAsync("http://localhost:8080/HelloWorldService.asmx", httpContent); // 处理响应 if (httpResponse.IsSuccessStatusCode) { string soapResponse = await httpResponse.Content.ReadAsStringAsync(); XmlDocument xmlDoc = new XmlDocument(); xmlDoc.LoadXml(soapResponse); string result = xmlDoc.GetElementsByTagName("HelloWorldResult")[0].InnerText; Console.WriteLine($"调用成功,返回结果:{result}"); } else { Console.WriteLine($"调用失败,响应状态码:{httpResponse.StatusCode}"); } } } } ``` 上述代码以调用 HelloWorld 接口为例,可以根据实际情况更改请求头信息和请求内容。此外,我们还需要处理响应。如果响应状态码为成功,我们可以解析出响应内容,并获取接口返回的结果。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值