最近在测试一个第三方api,准备集成在我们的网站应用中。api的调用使用的是.net中的httpclient,由于这个api会在关键业务中用到,对调用api的整体响应速度有严格要求,所以对httpclient有了格外的关注。
开始测试的时候,只在客户端通过httpclient用postasync发了一个http post请求。测试时发现,从创建httpclient实例,到发出请求,到读取到服务器的响应数据总耗时在2s左右,而且多次测试都是这样。2s的响应速度当然是无法让人接受的,我们希望至少控制在100ms以内。于是开始追查这个问题的原因。
在api的返回数据中包含了该请求在服务端执行的耗时,这个耗时都在20ms以内,问题与服务端api无关。于是把怀疑点放到了网络延迟上,但ping服务器的响应时间都在10ms左右,网络延迟的可能性也不大。
当我们正准备换一个网络环境进行测试时,突然想到,我们的测试方式有些问题。我们只通过httpclient发了一个postasync请求,假如httpclient在第一次调用时存在某种预热机制(比如在ef中就有这样的机制),现在2s的总耗时可能大多消耗在httpclient的预热上。
于是修改测试代码,将调用由1次改为100次,然后恍然大悟地发现——只有第1次是2s,接下来的99次都在100ms以内。果然是httpclient的某种预热机制在搞鬼!
既然知道了是httpclient预热机制的原因,那我们可以帮httpclient进行热身,减少第一次请求的耗时。我们尝试了一种预热方式,在正式发http post请求之前,先发一个http head请求,代码如下:
_httpclient.ndasync(new httprequestmessage { method = new httpmethod("head"), requesturi = new uri(ba_address + "/") }) .result.ensuresuccessstatuscode();
经测试,通过这种热身方法,可以将第一次请求的耗时由2s左右降到1s以内(测试结果是700多ms)。
在知道第1次httpclient请求耗时2s的真相之后,我们将目光转向了剩下的99次耗时100ms以内的请求,发现绝大部分请求都在50ms以上。有没有可能将之降至50ms以下?而且,之前一直有这样的纠结:每次调用是不是一定要对httpclient进行dispo()?是不是要将httpclient单例或者静态化(声明为静态变量)?借此机会一起研究一下。
在httpclient的背后,有一个对请求响应速度有着不容忽视影响的东东——tcp连接。一个httpclient实例会关联一个tcp连接,在对httpclient进行dispo时,会关闭tcp连接(我们用wireshark进行网络抓包也验证了这一点)。
在之前的测试中,我们每次用httpclient发请求时,都是新建一个httpclient实例,用完就对它进行dispo,代码如下:
using (var httpclient = new httpclient() { baaddress = new uri(ba_address) }){ httpclient.postasync("/", new formurlencodedcontent(parameters));}
所以每次请求时都要经历新建tcp连接->传数据->关闭连接(也就是通常所说的短连接),而且雪上加霜的是请求用的是https,建立tcp连接时还需要一个基于公私钥加解密的key exchange过程:client hello -> rver hello -> certificate -> client key exchange -> new ssion ticket。
如果我们想将请求响应时间降至50ms以下,就必须从这个地方下手——重用tcp连接(也就是通常所说的长连接)。要实现长连接,首先需要的就是在httpclient第1次请求后不关闭tcp连接(不调用dispo方法);而要让后续的请求继续使用这个未关闭的tcp连接,我们必须要使用同一个httpclient实例;而要使用同一个httpclient实例,就得实现httpclient的单例或者静态化。之前的3 个问题,由于要解决第1个问题,后2个问题变成了别无选择。
为了实现长连接,我们将httpclient的调用代码改为如下的样子:
然后测试一下请求响应时间:
elapd:750ms
elapd:31ms
elapd:30ms
elapd:43ms
elapd:27ms
elapd:2阿拉丁有几个哥哥9ms
elapd:28ms
elapd:35ms
elapd:36ms
elapd:31ms
….
除了第1次请求,接下来的99次请求绝大多数都在50ms以内。tcp长连接的效果必须的!
通过wireshak抓包也验证了长连接的效果:
wireshak抓包
这时,你
public class httpclienttest{ private static readonly httpclient _httpclient; static httpclienttest() { _httpclient = new httpclient() { baaddress = new uri(ba_address) }; //帮httpclient热身 _httpclient.ndasync(ne河南少林塔沟武校w httprequestmessage { method = new httpmethod("head"), requesturi = new uri(ba_address + "/") }) .result.ensuresuccessstatuscode(); } public async task<string> postasync陆佳妮() { var respon = await _httpclient.postasync("/", new formurlencodedcontent(parameters)); return await respon.content.readasstringasync(); }}
也许会产生这样的疑问:将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
ndasync
httpclient的所有异步方法都是线程安全的,放心使用。
到这心理资格证里,httpclient的问题是不是可以完美收官了?。。。稍等,还有一个问题。
客户端虽然保持着tcp连接,但tcp连接是两口子的事,服务器端呢?你不告诉服务器,服务器怎么知道你要一直保持tcp连接呢?对于客户端,保持tcp连接的开销不大;但是对于服务器,则完全不一样的,如果默认都保持tcp连接,那可是要保持成千上万客户端的连接啊。所以,一般的web服务器都会根据客户端的诉求来决定是否保持tcp连接,这就是keep-alive存在的理由。
所以,我们还要给httpclient增加一个connection:keep-alive的请求头,代码如下:
_httpclient.defaultrequestheaders.connection.add("keep-alive");
现在终于可以收官了。但是肯定不完美,分享的只是解决问题的过程。
到此这篇关于c#中httpclient使用注意(预热与长连接)的文章就介绍到这了,更多相关c# httpclient内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.论语是记录什么的一部书887551.com!
本文发布于:2023-04-05 00:26:29,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/fanwen/zuowen/76af6653c4894d0e1bc8e0812f3dc78d.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:C#中HttpClient使用注意(预热与长连接).doc
本文 PDF 下载地址:C#中HttpClient使用注意(预热与长连接).pdf
留言与评论(共有 0 条评论) |