.NET 中非常奇怪的 SSL 错误:无法仅为特定 URL 解密指定的数据
Very weird SSL error in .NET: The specified data could not be decrypted only for a specific URL
我正在使用 .NET 从 URL 下载数据。对于大多数 URLs 它没有问题,但是对于一个特定的 URL,我在尝试建立连接时遇到了一个非常奇怪的错误。此外,该错误仅在第二次(及后续)尝试发出请求时发生。第一次似乎总能成功。
下面是一些演示问题的示例代码:
string url = "https://health-infobase.canada.ca/src/data/covidLive/covid19.csv";
for (int i = 1; i <= 10; i++)
{
var req = (HttpWebRequest)WebRequest.Create(url);
// Just in case, rule these out as being related to the issue.
req.AllowAutoRedirect = false;
req.ServerCertificateValidationCallback = (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => true;
try
{
// This line throws the exception.
using (req.GetResponse()) { }
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
Console.WriteLine($"Failed on attempt {i}.");
return;
}
}
备注:
- 使用指定以外的任何其他 URL 似乎都可行。即使是同一台服务器上的其他 URL(具有相同的证书)也可以毫无问题地工作。例如,https://health-infobase.canada.ca/pass.
- 我将 SChannel 日志记录级别提高到 3(警告和错误),但在 Windows 事件日志中没有看到来自 SChannel 源的任何内容。
- 问题发生在 .NET 4.8 (528372) 和 .NET Core 3.1.7
WebRequest
和 WebClient
都会出现问题
- 在 .NET Framework 4.8 中,使用
WebClient.DownloadData()
时问题似乎消失了,但使用 WebClient.OpenRead()
时问题仍然存在
- 在 .NET Framework 4.8 中,问题似乎只发生在 URL 下载某些文件(如我的代码示例中的文件)的情况下。然而,在 .NET Core 中,任何 URL 路径在 https://health-infobase.canada.ca/src/.
下都会发生错误
- 如果我使用中介 HTTPS 嗅探器(如 Fiddler),那么问题就会消失。
- 运行 Linux 上的 .NET Core 上的相同代码没有出现任何问题。
- 使用硬连线证书验证回调(
ServicePointManager.ServerCertificateValidationCallback
总是 returns true
)没有帮助。
我在 .NET Core 中 运行 时的堆栈跟踪如下所示:
System.Net.WebException: The SSL connection could not be established, see inner exception. Authentication failed, see inner exception.
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception (0x80090330): The specified data could not be decrypted.
--- End of inner exception stack trace ---
at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
at System.Net.Security.SslStream.ThrowIfExceptional()
at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__65_1(IAsyncResult iar)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at System.Net.HttpWebRequest.SendRequest()
at System.Net.HttpWebRequest.GetResponse()
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.GetResponse()
at UserQuery.Main() in C:\Users\robs\AppData\Local\Temp\LINQPad6\_gifldqtg\xltrxu\LINQPadQuery:line 12
在 .NET Framework 上,堆栈跟踪似乎用处不大:
System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.
at System.Net.HttpWebRequest.GetResponse()
at UserQuery.Main() in C:\Users\robs\AppData\Local\Temp\LINQPad5\_psduzptv\dcrjhq\LINQPadQuery.cs:line 48
更新:作为问题提交于 github:https://github.com/dotnet/runtime/issues/43682
更新
我深入了解并尝试强行关闭与服务器的 SSL 连接。遗憾的是,没有 API 或者我只是没有找到。所以我们将玩一下反射。
for (int i = 1; i <= 25; i++)
{
try
{
Console.WriteLine(i);
// use new instances everytime now
using var handler = new HttpClientHandler();
using var client = new HttpClient(handler);
// I found the stream contains a reference to the connection...
await using var test = await client.GetStreamAsync(url);
var connectionField = test.GetType()
.GetField("_connection", BindingFlags.Instance | BindingFlags.NonPublic);
var connection = connectionField.GetValue(test);
// ...which contains a reference to the SslStream...
var sslStreamField = connection.GetType()
.GetField("_stream", BindingFlags.Instance | BindingFlags.NonPublic);
var sslStream = sslStreamField.GetValue(connection) as SslStream;
using var sr = new StreamReader(test);
var data = await sr.ReadToEndAsync();
Console.WriteLine(data.Substring(0, 100));
// ... which I'll shutdown now.
sslStream.Close();
sslStream.Dispose();
await Task.Delay(1000);
}
catch (Exception ex)
{
Console.WriteLine($"Failed on attempt {i} : {ex.Message}.");
}
}
这工作正常,我们在 Wireshark 中看到我们的朋友 FIN、ACK。有趣的是,错误仍然以交替模式出现。也许有一些损坏的负载平衡器或正在发生的事情 - 我不知道。
这是输出
1
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
2
Failed on attempt 2 : The SSL connection could not be established, see inner exception..
3
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
4
Failed on attempt 4 : The SSL connection could not be established, see inner exception..
5
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
6
Failed on attempt 6 : The SSL connection could not be established, see inner exception..
7
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
8
Failed on attempt 8 : The SSL connection could not be established, see inner exception..
9
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
10
Failed on attempt 10 : The SSL connection could not be established, see inner exception..
11
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
12
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
13
Failed on attempt 13 : The SSL connection could not be established, see inner exception..
14
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
15
Failed on attempt 15 : The SSL connection could not be established, see inner exception..
16
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
17
Failed on attempt 17 : The SSL connection could not be established, see inner exception..
18
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
19
Failed on attempt 19 : The SSL connection could not be established, see inner exception..
20
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
旧答案(更好的 IMO)
不确定这里的问题是什么,但是好吧,这里有一个解决方法。
static string url = "https://health-infobase.canada.ca/src/data/covidLive/covid19.csv";
static async Task Fix1()
{
var client = new HttpClient();
for (int i = 1; i <= 25; i++)
{
var response = await client.GetAsync(url);
// .. do something
}
}
static async Task Fix2()
{
var handler = new HttpClientHandler();
for (int i = 1; i <= 25; i++)
{
var client = new HttpClient(handler);
var response = await client.GetAsync(url);
// .. do something
}
}
看起来确实很奇怪。保留客户端或处理程序将使连接保持活动状态并防止另一次密钥交换。
让我们再次修改您的示例并发送一个 UDP 触发数据包,这样我们就可以看到在 GetResponse 之后会发生什么。
static async Task WhatIsGoingOn()
{
UdpClient udp = new UdpClient();
for (int i = 1; i <= 25; i++)
{
var req = (HttpWebRequest)WebRequest.CreateHttp(url);
using var data = req.GetResponse();
await udp.SendAsync(new byte[] { 1, 2, 3, 4 }, 4, IPEndPoint.Parse("255.255.255.255"));
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
await Task.Delay(200);
}
}
在Wireshark中,我们会看到发送触发数据包时传输仍在进行中。另一个 TLS 连接被发起并被服务器重置。也许限制速率或限制与服务器的活动连接?
如果您采用原始循环并通过睡眠进行节流/Task.Delays,您将获得 3/4 或更多的“成功”连接。
即使块作用域丢失并且 using 应该完成它的工作或者如果手动调用 Dispose,连接仍然存在的事实实际上非常奇怪。
根据 https://github.com/dotnet/runtime/issues/43682 中的最新信息,这似乎是 Windows 中的一个 OS 错误。将 Windows 更新到最新版本可解决此问题。
我正在使用 .NET 从 URL 下载数据。对于大多数 URLs 它没有问题,但是对于一个特定的 URL,我在尝试建立连接时遇到了一个非常奇怪的错误。此外,该错误仅在第二次(及后续)尝试发出请求时发生。第一次似乎总能成功。
下面是一些演示问题的示例代码:
string url = "https://health-infobase.canada.ca/src/data/covidLive/covid19.csv";
for (int i = 1; i <= 10; i++)
{
var req = (HttpWebRequest)WebRequest.Create(url);
// Just in case, rule these out as being related to the issue.
req.AllowAutoRedirect = false;
req.ServerCertificateValidationCallback = (object s, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => true;
try
{
// This line throws the exception.
using (req.GetResponse()) { }
}
catch (Exception ex) {
Console.WriteLine(ex.ToString());
Console.WriteLine($"Failed on attempt {i}.");
return;
}
}
备注:
- 使用指定以外的任何其他 URL 似乎都可行。即使是同一台服务器上的其他 URL(具有相同的证书)也可以毫无问题地工作。例如,https://health-infobase.canada.ca/pass.
- 我将 SChannel 日志记录级别提高到 3(警告和错误),但在 Windows 事件日志中没有看到来自 SChannel 源的任何内容。
- 问题发生在 .NET 4.8 (528372) 和 .NET Core 3.1.7
WebRequest
和WebClient
都会出现问题
- 在 .NET Framework 4.8 中,使用
WebClient.DownloadData()
时问题似乎消失了,但使用WebClient.OpenRead()
时问题仍然存在
- 在 .NET Framework 4.8 中,问题似乎只发生在 URL 下载某些文件(如我的代码示例中的文件)的情况下。然而,在 .NET Core 中,任何 URL 路径在 https://health-infobase.canada.ca/src/. 下都会发生错误
- 如果我使用中介 HTTPS 嗅探器(如 Fiddler),那么问题就会消失。
- 运行 Linux 上的 .NET Core 上的相同代码没有出现任何问题。
- 使用硬连线证书验证回调(
ServicePointManager.ServerCertificateValidationCallback
总是 returnstrue
)没有帮助。
我在 .NET Core 中 运行 时的堆栈跟踪如下所示:
System.Net.WebException: The SSL connection could not be established, see inner exception. Authentication failed, see inner exception.
---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception.
---> System.Security.Authentication.AuthenticationException: Authentication failed, see inner exception.
---> System.ComponentModel.Win32Exception (0x80090330): The specified data could not be decrypted.
--- End of inner exception stack trace ---
at System.Net.Security.SslStream.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReceiveBlob(Byte[] buffer, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)
at System.Net.Security.SslStream.PartialFrameCallback(AsyncProtocolRequest asyncRequest)
--- End of stack trace from previous location where exception was thrown ---
at System.Net.Security.SslStream.ThrowIfExceptional()
at System.Net.Security.SslStream.InternalEndProcessAuthentication(LazyAsyncResult lazyResult)
at System.Net.Security.SslStream.EndProcessAuthentication(IAsyncResult result)
at System.Net.Security.SslStream.EndAuthenticateAsClient(IAsyncResult asyncResult)
at System.Net.Security.SslStream.<>c.<AuthenticateAsClientAsync>b__65_1(IAsyncResult iar)
at System.Threading.Tasks.TaskFactory`1.FromAsyncCoreLogic(IAsyncResult iar, Func`2 endFunction, Action`1 endAction, Task`1 promise, Boolean requiresSynchronization)
--- End of stack trace from previous location where exception was thrown ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.EstablishSslConnectionAsyncCore(Stream stream, SslClientAuthenticationOptions sslOptions, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at System.Net.HttpWebRequest.SendRequest()
at System.Net.HttpWebRequest.GetResponse()
--- End of inner exception stack trace ---
at System.Net.HttpWebRequest.GetResponse()
at UserQuery.Main() in C:\Users\robs\AppData\Local\Temp\LINQPad6\_gifldqtg\xltrxu\LINQPadQuery:line 12
在 .NET Framework 上,堆栈跟踪似乎用处不大:
System.Net.WebException: The request was aborted: Could not create SSL/TLS secure channel.
at System.Net.HttpWebRequest.GetResponse()
at UserQuery.Main() in C:\Users\robs\AppData\Local\Temp\LINQPad5\_psduzptv\dcrjhq\LINQPadQuery.cs:line 48
更新:作为问题提交于 github:https://github.com/dotnet/runtime/issues/43682
更新
我深入了解并尝试强行关闭与服务器的 SSL 连接。遗憾的是,没有 API 或者我只是没有找到。所以我们将玩一下反射。
for (int i = 1; i <= 25; i++)
{
try
{
Console.WriteLine(i);
// use new instances everytime now
using var handler = new HttpClientHandler();
using var client = new HttpClient(handler);
// I found the stream contains a reference to the connection...
await using var test = await client.GetStreamAsync(url);
var connectionField = test.GetType()
.GetField("_connection", BindingFlags.Instance | BindingFlags.NonPublic);
var connection = connectionField.GetValue(test);
// ...which contains a reference to the SslStream...
var sslStreamField = connection.GetType()
.GetField("_stream", BindingFlags.Instance | BindingFlags.NonPublic);
var sslStream = sslStreamField.GetValue(connection) as SslStream;
using var sr = new StreamReader(test);
var data = await sr.ReadToEndAsync();
Console.WriteLine(data.Substring(0, 100));
// ... which I'll shutdown now.
sslStream.Close();
sslStream.Dispose();
await Task.Delay(1000);
}
catch (Exception ex)
{
Console.WriteLine($"Failed on attempt {i} : {ex.Message}.");
}
}
这工作正常,我们在 Wireshark 中看到我们的朋友 FIN、ACK。有趣的是,错误仍然以交替模式出现。也许有一些损坏的负载平衡器或正在发生的事情 - 我不知道。
这是输出
1
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
2
Failed on attempt 2 : The SSL connection could not be established, see inner exception..
3
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
4
Failed on attempt 4 : The SSL connection could not be established, see inner exception..
5
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
6
Failed on attempt 6 : The SSL connection could not be established, see inner exception..
7
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
8
Failed on attempt 8 : The SSL connection could not be established, see inner exception..
9
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
10
Failed on attempt 10 : The SSL connection could not be established, see inner exception..
11
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
12
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
13
Failed on attempt 13 : The SSL connection could not be established, see inner exception..
14
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
15
Failed on attempt 15 : The SSL connection could not be established, see inner exception..
16
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
17
Failed on attempt 17 : The SSL connection could not be established, see inner exception..
18
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
19
Failed on attempt 19 : The SSL connection could not be established, see inner exception..
20
pruid,prname,prnameFR,date,numconf,numprob,numdeaths,numtotal,numtested,numrecover,percentrecover,ra
旧答案(更好的 IMO)
不确定这里的问题是什么,但是好吧,这里有一个解决方法。
static string url = "https://health-infobase.canada.ca/src/data/covidLive/covid19.csv";
static async Task Fix1()
{
var client = new HttpClient();
for (int i = 1; i <= 25; i++)
{
var response = await client.GetAsync(url);
// .. do something
}
}
static async Task Fix2()
{
var handler = new HttpClientHandler();
for (int i = 1; i <= 25; i++)
{
var client = new HttpClient(handler);
var response = await client.GetAsync(url);
// .. do something
}
}
看起来确实很奇怪。保留客户端或处理程序将使连接保持活动状态并防止另一次密钥交换。
让我们再次修改您的示例并发送一个 UDP 触发数据包,这样我们就可以看到在 GetResponse 之后会发生什么。
static async Task WhatIsGoingOn()
{
UdpClient udp = new UdpClient();
for (int i = 1; i <= 25; i++)
{
var req = (HttpWebRequest)WebRequest.CreateHttp(url);
using var data = req.GetResponse();
await udp.SendAsync(new byte[] { 1, 2, 3, 4 }, 4, IPEndPoint.Parse("255.255.255.255"));
GC.Collect();
GC.WaitForFullGCComplete();
GC.WaitForPendingFinalizers();
await Task.Delay(200);
}
}
在Wireshark中,我们会看到发送触发数据包时传输仍在进行中。另一个 TLS 连接被发起并被服务器重置。也许限制速率或限制与服务器的活动连接?
如果您采用原始循环并通过睡眠进行节流/Task.Delays,您将获得 3/4 或更多的“成功”连接。
即使块作用域丢失并且 using 应该完成它的工作或者如果手动调用 Dispose,连接仍然存在的事实实际上非常奇怪。
根据 https://github.com/dotnet/runtime/issues/43682 中的最新信息,这似乎是 Windows 中的一个 OS 错误。将 Windows 更新到最新版本可解决此问题。