扫二维码与项目经理沟通
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流
这篇文章主要讲解了“有关.NET Core HttpClient请求异常的问题”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“有关.NET Core HttpClient请求异常的问题”吧!
十年的哈尔滨网站建设经验,针对设计、前端、开发、售后、文案、推广等六对一服务,响应快,48小时及时工作处理。营销型网站的优势是能够根据用户设备显示端的尺寸不同,自动调整哈尔滨建站的显示方式,使网站能够适用不同显示终端,在浏览器中调整网站的宽度,无论在任何一种浏览器上浏览网站,都能展现优雅布局与设计,从而大程度地提升浏览体验。创新互联建站从事“哈尔滨网站设计”,“哈尔滨网站推广”以来,每个客户项目都认真落实执行。
场景:将相关厂家地磁设备(停车进出场)推送数据,转发至对接方。
最近一个星期经过观察会出现两种异常情况,一种是请求连接操作被取消,另外一种则是请求处理过程中操作被取消,具体异常信息请见如下图
我们知道HttpClient默认超时时间为100s,但项目默认设置请求超时时间为30s,初次分析异常情况来看,请求超时导致请求连接被取消异常,首先我telnet对接方端口通畅,于是乎与对接方交涉,是否存在从请求到对接方接口有额外前置处理,以及网络是否存在波动等等,排查得知相关猜测都予以否决,网络无任何问题
既然网络没有任何问题,难道是对接方即服务端处理数据量巨大,导致请求应答超时?于是乎对接方甩出几张最近消息接收数据
从上述两张图来看,最多的一天也才90来万,最近几天请求失败的数据大概2百来条,从我们平台打印日志来看,每秒请求大致是10个左右,通过HTTP对接完全可以承载。
然后,因为我们将数据(JSON,数据大小几乎可以忽略不计)转发到对接方,对接方拿到数据后不会进行任何额外处理,直接存储,所以我们请求超时时间30s,怎么会导致超时而引发异常呢?很奇怪
没辙了,只能拿起终极武器,tcp抓包分析,重新学习了tcp/ip协议族一波
为打开分析上述pcap文件,提前安装抓包软件WireShark最新版本,由于软件项目部署在Linux上,我们通过如下命令进行抓包
tcpdump -i any port 1443 -w exception.pcap
从如下抓包信息可以直接知道,对接方使用了HTTPS协议,具体请看如下图
如图
默认打开如上图所示,其中time为时间戳,为找到我们指定时间点(2021-06-04 15:46:17.296),通过tab:视图-时间显示格式-日期和时间
接下来我们找到包文件中导致请求被取消异常的具体时间节点(2021-06-04 15:46:17.296)
在TCP协议中RST表示复位,用于异常时关闭连接。在发送RST包关闭连接时,不必等待缓冲区的包都发出去,直接丢弃缓冲区的包而发送RST包。而接收端收到RST包后,也不必发送ACK包来确认。
好像有点眉头了,继续往下看
好家伙,结合这张图来看,基本上可以得出结论:原来是我们平台主动重置了连接,紧接着又开始了多次进行三次握手连接即(SYN、SYN/ACK、ACK)
推送逻辑是在类库中使用HttpClient,所以没有使用HttpClientFactory,因此定义静态变量来使用HttpClient,而非每一个请求就实例化一个HttpClient
接下来我们来详细分析项目示例代码并对其进行改进
static class Program { static HttpClient httpClient = CreateHttpClient(); static Program() { ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12; ServicePointManager.ServerCertificateValidationCallback = (message, cert, chain, error) => true, } static async Task Main(string[] args) { await httpClient.PostAsync("", new StringContent("")); } static HttpClient CreateHttpClient() { var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true }) { Timeout = TimeSpan.FromSeconds(30) }; client.DefaultRequestHeaders.Accept.Clear(); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Add("ContentType", "application/json"); return client; } }
若对接方仅使用HTTPS协议,无需验证证书,最好是忽略证书验证,否则有可能会引起建立验证证书连接异常,即添加
ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true
我们观察上述代码,有两个地方都对证书验证进行了设置,一个是在静态构造函数中ServicePointManager(简称SP),另外则在实例化HttpClient构造函数中即HttpClientHandler(简称HCH),那么这二者是否有使用上的限制呢?
在.NET Framework中,内置的HttpClient建立在HttpWebRequest之上,因此可以使用SP来配置
在.NET Core中,通过SP配置证书信息仅影响HttpWebRequest,而对HttpClient无效,需通过HCH配置来达到相同目的
所以去除在静态构造函数中对忽略证书的配置,改为在HttpClientHandler中
var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true, SslProtocols = SslProtocols.Tls12 })
回到本文的话题,为什么会重置连接即主动关闭连接呢?我们已分析过,和上述配置30s超时没有关系,主要有两方面原因
翻开并温习《图解HTTP》一书,如果请求频繁,最好建立持久连接,减少TCP连接的重复建立和断开所造成的额外开销,从而减轻服务端负载即重用HTTP连接,也称为Http keep-alive,持久连接的特点是,只要任意一方没有明确提出断开连接,否则保持TCP连接状态
配置keep-alive我们俗称为保活机制,所以在默认请求头中添加如下一行
//增加保活机制,表明连接为长连接 client.DefaultRequestHeaders.Connection.Add("keep-alive");
上述只是在报文头中添加持久化连接标识,但不意味着就一定生效,因为默认是禁用持久化连接,所以为了保险起见,添加如下代码
//启用保活机制(保持活动超时设置为 2 小时,并将保持活动间隔设置为 1 秒。) ServicePointManager.SetTcpKeepAlive(true, 7200000, 1000);
有个让我很疑惑的问题,通过查看设置启用持久化连接源码得知,这样设置意义在哪里?没弄明白,源码如下
public static void SetTcpKeepAlive(bool enabled, int keepAliveTime, int keepAliveInterval) { if (enabled) { if (keepAliveTime <= 0) { throw new ArgumentOutOfRangeException(nameof(keepAliveTime)); } if (keepAliveInterval <= 0) { throw new ArgumentOutOfRangeException(nameof(keepAliveInterval)); } } }
最关键的一点则是默认持久化连接数为2,非持久化连接为4
public class ServicePointManager { public const int DefaultNonPersistentConnectionLimit = 4; public const int DefaultPersistentConnectionLimit = 2; private ServicePointManager() { } }
那么问题是否就已很明了,项目中使用非持久化连接,即连接为4,未深究源码具体细节,大胆猜想一下,若连接大于4,是否会出现将此前连接主动关闭,重建新的连接请求呢?
最终我们讲原始代码修改为如下形式
static class Program { static HttpClient httpClient = CreateHttpClient(); static Program() { //默认连接数限制为2,增加连接数限制 ServicePointManager.DefaultConnectionLimit = 512; //启用保活机制(保持活动超时设置为 2 小时,并将保持活动间隔设置为 1 秒。) ServicePointManager.SetTcpKeepAlive(true, 7200000, 1000); } static async Task Main(string[] args) { await httpClient.PostAsync("", new StringContent("")); Console.WriteLine("Hello World!"); } static HttpClient CreateHttpClient() { var client = new HttpClient(new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, error) => true, SslProtocols = SslProtocols.Tls12 }) { Timeout = TimeSpan.FromSeconds(30) }; client.DefaultRequestHeaders.Accept.Clear(); //增加保活机制,表明连接为长连接 client.DefaultRequestHeaders.Connection.Add("keep-alive"); client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); client.DefaultRequestHeaders.Add("ContentType", "application/json"); return client; } }
进行如上设置后,通过一天观察,再未出现相关异常,至此问题解决告一段落,希望没有后续......
强烈建议:利用HttpClient发送请求设置持久化连接和根据实际业务评估增加连接数,而非默认的持久化连接为2,非持久化连接为4,否则极易出现相关异常。
引发思考:利用HttpClientFactory创建HttpClient是否有默认连接限制,据我所知,好像可以通过属性MaxConnectionsPerServer来配置
若非常清楚默认连接数限制,可能并算不上什么问题,也不存在如此诸多分析,不过对于我而言,收获的是在问题排查过程中,对可能干扰信息的过滤、筛选、确认以及对网络协议进一步的理解加深。
在.NET Core和.NET Framework中相关配置还是有些变化,最好是根据对应版本在官网上确认下
感谢各位的阅读,以上就是“有关.NET Core HttpClient请求异常的问题”的内容了,经过本文的学习后,相信大家对有关.NET Core HttpClient请求异常的问题这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!
我们在微信上24小时期待你的声音
解答本文疑问/技术咨询/运营咨询/技术建议/互联网交流