C# HttpClient在Linux VPS请求超时(ping、curl正常无防火墙)
我希望通过HttpClient获取页面内容,编写了如下简单方法,该方法在Visual Studio中运行正常。
private async Task<string> Connection(string Url) { using HttpClient client = new(); client.DefaultRequestHeaders.Add("Accept", "application/json"); client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 OPR/77.0.4054.277"); client.DefaultRequestHeaders.Add("Cache-Control", "no-cache, no-store, must-revalidate"); client.DefaultRequestHeaders.Add("Accept-Language", "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"); client.DefaultRequestHeaders.Add("Pragma", "no-cache"); double timestamp = GlobalMethod.Timestamp(); string json = await client.GetStringAsync(Url); return json; }
但当我在OVH托管的Ubuntu Linux VPS上运行应用(运行环境为.NET 5)时,HttpClient返回超时错误。
该VPS上ping目标地址、使用curl请求均正常,Postman使用相同参数请求也无问题,不存在防火墙拦截。
API链接:https://api.store.nvidia.com/partner/v1/feinventory?skus=FRNVGFT070NVGFT080NVGFT090NVLKR30SNSHRMT01NVGFT060T~187&locale=FR
超时错误信息如下:
System.Threading.Tasks.TaskCanceledException: The request was canceled due to the configured HttpClient.Timeout of 100 seconds elapsing.
---> System.TimeoutException: The operation was canceled.
---> System.Threading.Tasks.TaskCanceledException: The operation was canceled.
---> System.IO.IOException: Unable to read data from the transport connection: Operation canceled.
---> System.Net.Sockets.SocketException (125): Operation canceled
--- End of inner exception stack trace ---
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.GetResult(Int16 token)
at System.Net.Security.SslStream.ReadAsyncInternal[TIOAdapter](TIOAdapter adapter, Memory`1 buffer)
at System.Net.Http.HttpConnection.FillAsync(Boolean async)
at System.Net.Http.HttpConnection.ReadNextResponseHeaderLineAsync(Boolean async, Boolean foldedHeadersAllowed)
at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
--- End of inner exception stack trace ---
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.GetStringAsyncCore(HttpRequestMessage request, CancellationToken cancellationToken)
at Castiel_Bot_Discord.Modules.NvidiaCommand.Connection(String Url) in C:\Users\gaeta\source\repos\Castiel_Bot_Discord\Castiel_Bot_Discord\Modules\NvidiaCommand.cs:line 114
at Castiel_Bot_Discord.Modules.NvidiaCommand.SearchGpu(String args) in C:\Users\gaeta\source\repos\Castiel_Bot_Discord\Castiel_Bot_Discord\Modules\NvidiaCommand.cs:line 42
问题根因
该问题是.NET 5在Linux环境下的HttpClient默认配置和Windows环境存在差异导致,核心触发点通常为以下两个:
- IPv6优先解析但VPS到目标API的IPv6链路不通,.NET 5的HttpClient默认不会自动降级到IPv4,而curl会自动尝试双栈并降级
- TLS版本/加密套件协商规则和curl不同,nvidia的API服务器拒绝了.NET发出的TLS握手请求
修复步骤
1. 调整HttpClient配置,强制使用IPv4+指定TLS版本
修改你的Connection方法,自定义HttpClientHandler替换默认配置,同时建议将HttpClient改为单例避免端口耗尽问题:
以下代码需要引入System.Net.Sockets、System.Net.Security、System.Security.Authentication命名空间
private static readonly HttpClient _client = new HttpClient(new SocketsHttpHandler { // 强制使用IPv4解析 ConnectCallback = async (context, cancellationToken) => { var entry = await Dns.GetHostEntryAsync(context.DnsEndPoint.Host, AddressFamily.InterNetwork, cancellationToken); var address = entry.AddressList[0]; var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true }; try { await socket.ConnectAsync(address, context.DnsEndPoint.Port, cancellationToken); return new NetworkStream(socket, ownsSocket: true); } catch { socket.Dispose(); throw; } }, // 限定支持的TLS版本 SslOptions = new SslClientAuthenticationOptions { EnabledSslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13 } }) { Timeout = TimeSpan.FromSeconds(30) }; private async Task<string> Connection(string Url) { _client.DefaultRequestHeaders.Clear(); _client.DefaultRequestHeaders.Add("Accept", "application/json"); _client.DefaultRequestHeaders.Add("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 OPR/77.0.4054.277"); _client.DefaultRequestHeaders.Add("Cache-Control", "no-cache, no-store, must-revalidate"); _client.DefaultRequestHeaders.Add("Accept-Language", "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7"); _client.DefaultRequestHeaders.Add("Pragma", "no-cache"); string json = await _client.GetStringAsync(Url); return json; }
2. 快速验证方案
如果不想先改代码,可以直接在VPS系统层面临时禁用IPv6验证根因:
sysctl -w net.ipv6.conf.all.disable_ipv6=1 sysctl -w net.ipv6.conf.default.disable_ipv6=1
如果禁用后请求正常,即可确认是IPv6解析导致的问题。
3. 长期优化建议
.NET 5已经停止官方支持,建议升级到.NET 6+,新版本的HttpClient已经优化了双栈解析的降级逻辑,默认行为和curl保持一致,可避免同类问题。
内容的提问来源于stack exchange,提问作者Castiel

