转自:http://www.cnblogs.com/dudu/p/csharp-httpclient-attention.html
最近在测试一个第三方API,准备集成在我们的网站应用中。API的调用使用的是.NET中的HttpClient,由于这个API会在关键业务中用到,对调用API的整体响应速度有严格要求,所以对HttpClient有了格外的关注。
在API的返回数据中包含了该请求在服务端执行的耗时,这个耗时都在20ms以内,问题与服务端API无关。于是把怀疑点放到了网络延迟上,但ping服务器的响应时间都在10ms左右,网络延迟的可能性也不大。
于是修改测试代码,将调用由1次改为100次,然后恍然大悟地发现——只有第1次是2s,接下来的99次都在100ms以内。果然是HttpClient的某种预热机制在搞鬼!
_httpClient.SendAsync(new HttpRequestMessage {
Method = new HttpMethod(“HEAD”),
RequestUri = new Uri(BASE_ADDRESS + “/”) })
.Result.EnsureSuccessStatusCode();
经测试,通过这种热身方法,可以将第一次请求的耗时由2s左右降到1s以内(测试结果是700多ms)。
在HttpClient的背后,有一个对请求响应速度有着不容忽视影响的东东——TCP连接。一个HttpClient实例会关联一个TCP连接,在对HttpClient进行Dispose时,会关闭TCP连接(我们用Wireshark进行网络抓包也验证了这一点)。
using (var httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) })
{
httpClient.PostAsync(“/”, new FormUrlEncodedContent(parameters));
}
所以每次请求时都要经历新建TCP连接->传数据->关闭连接(也就是通常所说的短连接),而且雪上加霜的是请求用的是https,建立TCP连接时还需要一个基于公私钥加解密的key exchange过程:Client Hello -> Server Hello -> Certificate -> Client Key Exchange -> New Session Ticket。
为了实现长连接,我们将HttpClient的调用代码改为如下的样子:
public class HttpClientTest
{
private static readonly HttpClient _httpClient;
static HttpClientTest()
{
_httpClient = new HttpClient() { BaseAddress = new Uri(BASE_ADDRESS) };
//帮HttpClient热身
_httpClient.SendAsync(new HttpRequestMessage {
Method = new HttpMethod(“HEAD”),
RequestUri = new Uri(BASE_ADDRESS + “/”) })
.Result.EnsureSuccessStatusCode();
}
public async Task PostAsync()
{
var response = await _httpClient.PostAsync(“/”, new FormUrlEncodedContent(parameters));
return await response.Content.ReadAsStringAsync();
}
}
然后测试一下请求响应时间:
Elapsed:750ms
Elapsed:31ms
Elapsed:30ms
Elapsed:43ms
Elapsed:27ms
Elapsed:29ms
Elapsed:28ms
Elapsed:35ms
Elapsed:36ms
Elapsed:31ms
….
除了第1次请求,接下来的99次请求绝大多数都在50ms以内。TCP长连接的效果必须的!
通过Wireshak抓包也验证了长连接的效果:
这时,你也许会产生这样的疑问:将HttpClient声明为静态变量,会不会存在线程安全问题?我们当时也有这样的疑问,后来在stackoverflow上找到了答案:
As per the comments below (thanks @ischell), the following instance methods are thread safe (all async):
CancelPendingRequests
DeleteAsync
GetAsync
GetByteArrayAsync
GetStreamAsync
GetStringAsync
PostAsync
PutAsync
SendAsync
HttpClient的所有异步方法都是线程安全的,放心使用。
到这里,HttpClient的问题是不是可以完美收官了?。。。稍等,还有一个问题。
客户端虽然保持着TCP连接,但TCP连接是两口子的事,服务器端呢?你不告诉服务器,服务器怎么知道你要一直保持TCP连接呢?对于客户端,保持TCP连接的开销不大;但是对于服务器,则完全不一样的,如果默认都保持TCP连接,那可是要保持成千上万客户端的连接啊。所以,一般的Web服务器都会根据客户端的诉求来决定是否保持TCP连接,这就是keep-alive存在的理由。
所以,我们还要给HttpClient增加一个Connection:keep-alive的请求头,代码如下:
_httpClient.DefaultRequestHeaders.Connection.Add(“keep-alive”);
现在终于可以收官了。但是肯定不完美,分享的只是解决问题的过程。
来自 http://www.cnblogs.com/dudu/p/csharp-httpclient-attention.html