.NET Framework HttpClient断网重连后报远程名称无法解析问题排查
设备为接入企业代理网络的笔记本,在应用中使用.NET Framework 4.8的HttpClient时,网络连通状态下请求正常;断开网络后抛出The remote name could not be resolved异常,符合预期。
但网络恢复、应用保持运行时,仍持续抛出The remote name could not be resolved异常,同环境下浏览器访问目标地址正常,重启应用后请求恢复正常。以下为整合多份同类异常解决方案编写的最小复现Demo。
是否是.NET底层内部存在缓存机制导致该问题?复现Demo中已实现每次请求新建HttpClient、新建HttpClientHandler、设置ConnectionClose请求头、调整ServicePointManager相关参数,如果确实存在底层缓存,要如何修改配置清除或调整缓存逻辑?
补充说明:在正式业务场景中,使用AddHttpClient<TI,T>方式注册HttpClient服务,对应依赖的服务类并非单例生命周期,仍存在完全相同的故障。
internal class Program { static async Task Main(string[] args) { var sp = ServicePointManager.FindServicePoint(new Uri("https://www.google.com")); sp.SetTcpKeepAlive(false,0,0); sp.ConnectionLeaseTimeout = (int)TimeSpan.FromSeconds(15).TotalMilliseconds; ServicePointManager.DnsRefreshTimeout = (int)TimeSpan.FromSeconds(15).TotalMilliseconds; while (true) { try { using (var client = new HttpClient(new HttpClientHandler())) { Console.WriteLine("Press Key to try send"); Console.ReadKey(); var uri = "https://www.google.com"; var message = new HttpRequestMessage(HttpMethod.Get, uri); message.Headers.ConnectionClose = true; message.Headers.CacheControl = new CacheControlHeaderValue() { MaxAge = TimeSpan.FromSeconds(1), }; var response = await client.SendAsync(message); Console.WriteLine("Success:"); Console.WriteLine(await response.Content.ReadAsStringAsync()); } } catch (Exception e) { Console.WriteLine(e); } } } }
进展1
感谢@Lex Li提供的追踪方法,通过查看系统网络追踪日志,发现如下现象:
- 请求1(网络连通状态):
System.Net Verbose: 0 : [25368] Entering ServicePoint#6044116::ServicePoint(myproxyaddress:8080) - 请求2(网络断开状态):
System.Net Verbose: 0 : [29900] Entering ServicePoint#3507177::ServicePoint(www.google.com:443) - 请求3(网络恢复状态):
System.Net Information: 0 : [29900] Associating HttpWebRequest#37200859 with ServicePoint#3507177
由此可见网络恢复后请求错误关联了断网时生成的直连ServicePoint实例。
如果将HttpClient和HttpClientHandler的实例化逻辑移到循环外,初始网络正常的场景下重连后可恢复正常;但如果应用启动时就处于断网状态,仍会因为关联错误的ServicePoint实例导致请求失败。初步判断需要在适用场景下显式指定代理配置,避免.NET自动探测代理时缓存错误的连接配置。
进展2
测试验证,显式指定代理地址确实可以解决该问题,可避免.NET自动探测连接路径时出现缓存错误,但硬编码代理配置的方式无法适配代理配置动态变更的场景。验证代码如下:
var handler = new HttpClientHandler() { Proxy = new WebProxy("myproxyadress:8080") }; var client = new HttpClient(handler);
内容的提问来源于stack exchange,提问作者XWIKO

