前言

最近项目上每天间断性捕获到HttpClient请求异常,感觉有点奇怪,于是乎观察了两三天,通过日志以及对接方沟通确认等等,查看对应版本源码,尝试添加部分配置发布后,观察十几小时暂无异常情况出现,貌似问题已得到解决,若有后续继续更新。HttpClient来源:netstandard2.0

异常问题

场景:将相关厂家地磁设备(停车进出场)推送数据,转发至对接方。最近一个星期经过观察会出现两种异常情况,一种是请求连接操作被取消,另外一种则是请求处理过程中操作被取消,具体异常信息请见如下图

我们知道HttpClient默认超时时间为100s,但项目默认设置请求超时时间为30s,初次分析异常情况来看,请求超时导致请求连接被取消异常,首先我telnet对接方端口通畅,于是乎与对接方交涉,是否存在从请求到对接方接口有额外前置处理,以及网络是否存在波动等等,排查得知相关猜测都予以否决,网络无任何问题

问题排查分析

既然网络没有任何问题,难道是对接方即服务端处理数据量巨大,导致请求应答超时?于是乎对接方甩出几张最近消息接收数据

从上述两张图来看,最多的一天也才90来万,最近几天请求失败的数据大概2百来条,从我们平台打印日志来看,每秒请求大致是10个左右,通过HTTP对接完全可以承载。然后,因为我们将数据(JSON,数据大小几乎可以忽略不计)转发到对接方,对接方拿到数据后不会进行任何额外处理,直接存储,所以我们请求超时时间30s,怎么会导致超时而引发异常呢?很奇怪,没辙了,只能拿起终极武器,tcp抓包分析,重新学习了tcp/ip协议族一波

WireShark抓包分析

为打开分析上述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之上,因此可以使用SC来配置

在.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请求异常详细情况分析的更多相关文章

  1. Web Api 中Get 和 Post 请求的多种情况分析

    转自:http://www.cnblogs.com/babycool/p/3922738.html 来看看对于一般前台页面发起的get和post请求,我们在Web API中要如何来处理. 这里我使用J ...

  2. Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析

    Volley源码解析(三) 有缓存机制的情况走缓存请求的源码分析 Volley之所以高效好用,一个在于请求重试策略,一个就在于请求结果缓存. 通过上一篇文章http://www.cnblogs.com ...

  3. [1.6W字] 浏览器跨域请求限制的详细原理分析&寻找一种最简单的方式实现XHR跨域(9种方法, 附大招可以纯前端实现跨域!)

    Title/ 浏览器跨域(CrossOrigin)请求的原理, 以及解决方案详细指南 #flight.Archives011 序: 最近看到又有一波新的创作活动了, 官方给出的话题中有一个" ...

  4. 【ASP.NET Core】处理异常--转

    老周写的[ASP.NET Core]处理异常非常的通俗易懂,拿来记录下. 转自老周:http://www.cnblogs.com/tcjiaan/p/8461408.html 今天咱们聊聊有关异常处理 ...

  5. .Net Core HttpClient处理响应压缩

    前言     在上篇文章[ASP.NET Core中的响应压缩]中我们谈到了在ASP.NET Core服务端处理关于响应压缩的请求,服务端的主要工作就是根据Content-Encoding头信息判断采 ...

  6. IIS提示“异常详细信息: System.Runtime.InteropServices.ExternalException: 无法执行程序”

    先来看错误提示: 无法执行程序.所执行的命令为 "C:/Windows/Microsoft.NET/Framework/v3.5/csc.exe" /noconfig /fullp ...

  7. 在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程)

    在浏览器中简单输入一个网址,解密其后发生的一切(http请求的详细过程) 原文链接:http://www.360doc.com/content/14/1117/10/16948208_42571794 ...

  8. MongoDB数据库索引构建情况分析

    前面的话 本文将详细介绍MongoDB数据库索引构建情况分析 概述 创建索引可以加快索引相关的查询,但是会增加磁盘空间的消耗,降低写入性能.这时,就需要评判当前索引的构建情况是否合理.有4种方法可以使 ...

  9. 使用 NLog 给 Asp.Net Core 做请求监控

    为了减少由于单个请求挂掉而拖垮整站的情况发生,给所有请求做统计是一个不错的解决方法,通过观察哪些请求的耗时比较长,我们就可以找到对应的接口.代码.数据表,做有针对性的优化可以提高效率.在 asp.ne ...

随机推荐

  1. linux中mysql连接不上,服务启动失败等问题解决

    confluence问题解决方式 1)针对confluence访问页面报500与连接失败等问题 首先我们登陆部署confluence的服器 10.15.4.115 2)重启mysql服务,发现重启失败 ...

  2. 重绘DevExpress的XtraMessageBox消息提示框控件

    先来看提示框,可以看到框其实是一个去掉最大化.最小化按钮后的窗体,窗体的内容就是我们想要提示的内容,重绘提示框其实就是重绘窗体以及中间部分的内容. 首先重绘窗体,消息提示框的窗体不是XtraForm而 ...

  3. Linux在shell终端中清空DNS缓存,刷新DNS的方法

    现在很多Linux发行版都没有内置DNS本地缓存,Linux不像Windows那样可以使用ipconfig /flushdns来刷新,在Linux下无需刷新,因为本身没有缓存. 前言 在Linux系统 ...

  4. Nginx 配置浏览Linux 系统目录并下载文件

    准备工作: 安装编译工具及库文件: yum -y install make zlib zlib-devel gcc-c++ libtool  openssl openssl-devel 安装PCRE( ...

  5. 百度sitemap.xml

    <?xml version="1.0" encoding="UTF-8" ?> <urlset xmlns="http://www. ...

  6. 感染性的木马病毒分析之样本KWSUpreport.exe

    一.病毒样本简述 初次拿到样本 KWSUpreport_感染.exe.v 文件,通过使用PE工具,并不能辨别出该样本是那种感染类型,使用了一个比较直接的方法,从网上查资料,获取到了该样本的正常EXE文 ...

  7. 逆向工程第004篇:跨越CM4验证机制的鸿沟(中)

    一.前言 在上一篇文章的最后,我已经找出了关键的CALL语句,那么这篇文章我就带领大家来一步一步地分析这个CALL.我会将我的思路完整地展现给大家,因此分析过程可能略显冗长,我会分为两篇文章进行讨论. ...

  8. Linux中正则表达式和字符串的查询、替换(tr/diff/wc/find)

    目录 正则表达式 基本正则表达式 扩展正则表达式 grep tr diff du wc find 正则表达式 正则表达式,又称正规表示法.常规表示法( Regular Expression,在代码中常 ...

  9. 如何以最简单的方式安装 KALI 渗透测试框架系统

    0x01 第一步下载 KALI 百度搜索 KALI 官网,找到下载区,我选的是 64 位标准版,但是推荐下载 32 位(功能貌似更全) 这个为下载后的 iso 镜像文件 0x02 第二步打开虚拟机,配 ...

  10. 4.PHP正则表达式与数组

    PHP正则表达式相关 行定位符 开头 ^tm 结尾 tm$ 不限制 tm 单词定界符 \btm\b   单词tm,如果想取反的话就是大写的 \Btm\B 或的关系,[Tt][Mm] 可以表达 tm T ...