HttpClient 和 ASP.NET Core 2.0 网络服务之间的连接已关闭错误
Connection was closed error between HttpClient and ASP.NET Core 2.0 webservice
我在 IIS 上有一个 ASP.NET Core 2.0 网络服务 运行ning。控制器的方法之一看起来或多或少像这样:
[HttpGet()]
public IActionResult Test()
{
// do some db updates and get data
var result = DoSomeStuff();
// serialize data to byte array
var output = Serialize(result);
return File(output, "application/octet-stream");
}
它进行一些数据库更新,从 table 查询记录,序列化数据并将它们作为响应发送。数据以二进制格式发送。我正在使用 MessagePack-CSharp 作为序列化程序。
然后我有客户端应用程序与此 web 服务进行通信。它是 .NET Standard 2.0 库,从 .NET 4.6.1 控制台应用程序引用。我使用 HttpClient
请求并使用 HttpResponseMessage.Content.ReadAsByteArrayAsync()
读取响应(具体代码见下文)。
我想做一些测试。我的 table 有 cca。 80 列并包含 cca。 140000条记录。所有这些都应该发送给客户。从 db 获取数据需要几秒钟,然后是序列化的所有内容和 cca 的结果。 34MB 发送给客户端。
我有 10 个客户。当他们连续调用 webservice 时,一切正常。当我同时对 web 服务和 fire 客户端施加压力时,我几乎总是在其中一些上出现错误(通常是一两个失败,有时甚至是 4-5 个)。
异常如下,它是从 ReadAsByteArrayAsync
调用中引发的:
System.AggregateException: One or more errors occurred. ---> System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
...
---> (Inner Exception #0) System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
...
我发现了几个与此类异常相关的 SO 线程(例如 ),所以我最初认为这是一个与客户端相关的问题。建议回答:
- 切换到 HTTP 1.0
- 设置
Connection: close
而不是Connection: keep-alive
- 反之同上
对我没有任何作用。我想我在某处读到 HttpClient 中存在一些错误(现在找不到源代码)。我尝试使用 Nuget 的最新 System.Net.Http
包。同样的问题。我创建了 .NET Core 控制台应用程序并使用 HttpClient
的核心版本。同样的问题。我使用 HttpWebRequest
而不是 HttpClient
。同样的潜在问题。
我在同一台虚拟机上 运行 网络服务和客户端。只是为了排除一些本地问题,我 运行 客户端同时来自其他计算机。同样的问题。
所以我最终得到了以下简化代码(只有一个应用程序有 10 个线程):
private async void Test_Click(object sender, RoutedEventArgs e)
{
try
{
var tasks = Enumerable.Range(1, 10).Select(async i => await Task.Run(async () => await GetContent(i))).ToList();
await Task.WhenAll(tasks);
MessageBox.Show(String.Join(Environment.NewLine, tasks.Select(t => t.Result.ToString())));
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private async Task<Int32> GetContent(Int32 id)
{
using (var httpClient = new HttpClient())
{
var url = "http://localhost/TestService/api/test";
using (var responseMessage = await httpClient.GetAsync(url).ConfigureAwait(false))
{
// just read everything and return length
// ReadAsByteArrayAsync throws sometimes an exception
var content = await responseMessage.Content.ReadAsByteArrayAsync();
return content.Length;
}
}
}
我对实际流量很好奇,所以我设置了 Fiddler。当错误发生时,Fiddler 显示响应确实已损坏,并且实际上只发送了假定数据量的一部分(6MB、20MB,...而不是 34MB)。好像它被随机打断了。我用 Wireshark 玩了一会儿,我看到 RST/ACK 数据包是从服务器发送的,但我分析这种低级通信还不够好。
所以,我专注于服务器端。当然,我仔细检查了控制器的方法是否有任何异常。一切正常。我将日志级别设置为跟踪并在日志中找到以下内容:
info: Microsoft.AspNetCore.Server.Kestrel[28]
Connection id "0HL89D9NUNEOQ", Request id "0HL89D9NUNEOQ:00000001": the connection was closed becuase the response was not read by the client at the specified minimum data rate.
dbug: Microsoft.AspNetCore.Server.Kestrel[10]
Connection id "0HL89D9NUNEOQ" disconnecting.
...
info: Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv[14]
Connection id "0HL89D9NUNEOQ" communication error.
Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking.UvException: Error -4081 ECANCELED operation canceled
我没有发现任何有趣的东西,ASP.NET 核心特定于此错误。根据 this documentation,IIS 有一个选项可以指定最小吞吐率,当它向客户端发送响应时,设置如下:
<system.applicationHost>
<webLimits minBytesPerSecond="0"/>
</system.applicationHost>
我在我的 Web.config
中使用它,但它没有任何效果(它应用于 ASP.NET 核心应用程序还是仅完整框架设置?)。
我尝试 return FileStreamResult
而不是 FileContentResult
,但还是没有用。
与客户端类似,我也试图为服务器端找到最少的可重现代码。方法只有 Thread.Sleep(8000)
(而不是数据库调用),然后生成随机的 50Mb 字节数组并 return 编辑它。这没有任何问题,所以我想我会继续朝那个方向调查。我知道数据库可能是这里的瓶颈,但不确定它是如何导致这种情况的(没有超时异常,没有死锁,...)。
有什么建议吗?我至少想知道它是服务器还是客户端相关问题。
您的吞吐量似乎低于最低数据速率。 Kestrel Fundamentals:
中描述了此行为
Kestrel checks every second if data is coming in at the specified rate in bytes/second. If the rate drops below the minimum, the connection is timed out. The grace period is the amount of time that Kestrel gives the client to increase its send rate up to the minimum; the rate is not checked during that time. The grace period helps avoid dropping connections that are initially sending data at a slow rate due to TCP slow-start.
The default minimum rate is 240 bytes/second, with a 5 second grace period.
A minimum rate also applies to the response. The code to set the request limit and the response limit is the same except for having RequestBody
or Response
in the property and interface names.
您可以在 Program.cs 中这样配置:
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.Limits.MinResponseDataRate = null;
})
将此选项设置为 null
表示不应强制执行最低数据速率。
我在 IIS 上有一个 ASP.NET Core 2.0 网络服务 运行ning。控制器的方法之一看起来或多或少像这样:
[HttpGet()]
public IActionResult Test()
{
// do some db updates and get data
var result = DoSomeStuff();
// serialize data to byte array
var output = Serialize(result);
return File(output, "application/octet-stream");
}
它进行一些数据库更新,从 table 查询记录,序列化数据并将它们作为响应发送。数据以二进制格式发送。我正在使用 MessagePack-CSharp 作为序列化程序。
然后我有客户端应用程序与此 web 服务进行通信。它是 .NET Standard 2.0 库,从 .NET 4.6.1 控制台应用程序引用。我使用 HttpClient
请求并使用 HttpResponseMessage.Content.ReadAsByteArrayAsync()
读取响应(具体代码见下文)。
我想做一些测试。我的 table 有 cca。 80 列并包含 cca。 140000条记录。所有这些都应该发送给客户。从 db 获取数据需要几秒钟,然后是序列化的所有内容和 cca 的结果。 34MB 发送给客户端。
我有 10 个客户。当他们连续调用 webservice 时,一切正常。当我同时对 web 服务和 fire 客户端施加压力时,我几乎总是在其中一些上出现错误(通常是一两个失败,有时甚至是 4-5 个)。
异常如下,它是从 ReadAsByteArrayAsync
调用中引发的:
System.AggregateException: One or more errors occurred. ---> System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
...
---> (Inner Exception #0) System.Net.Http.HttpRequestException: Error while copying content to a stream. ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host
at System.Net.Sockets.Socket.EndReceive(IAsyncResult asyncResult)
at System.Net.Sockets.NetworkStream.EndRead(IAsyncResult asyncResult)
--- End of inner exception stack trace ---
at System.Net.ConnectStream.EndRead(IAsyncResult asyncResult)
at System.IO.Stream.<>c.<BeginEndReadAsync>b__43_1(Stream stream, IAsyncResult asyncResult)
at System.Threading.Tasks.TaskFactory`1.FromAsyncTrimPromise`1.Complete(TInstance thisRef, Func`3 endMethod, IAsyncResult asyncResult, Boolean requiresSynchronization)
...
我发现了几个与此类异常相关的 SO 线程(例如
- 切换到 HTTP 1.0
- 设置
Connection: close
而不是Connection: keep-alive
- 反之同上
对我没有任何作用。我想我在某处读到 HttpClient 中存在一些错误(现在找不到源代码)。我尝试使用 Nuget 的最新 System.Net.Http
包。同样的问题。我创建了 .NET Core 控制台应用程序并使用 HttpClient
的核心版本。同样的问题。我使用 HttpWebRequest
而不是 HttpClient
。同样的潜在问题。
我在同一台虚拟机上 运行 网络服务和客户端。只是为了排除一些本地问题,我 运行 客户端同时来自其他计算机。同样的问题。
所以我最终得到了以下简化代码(只有一个应用程序有 10 个线程):
private async void Test_Click(object sender, RoutedEventArgs e)
{
try
{
var tasks = Enumerable.Range(1, 10).Select(async i => await Task.Run(async () => await GetContent(i))).ToList();
await Task.WhenAll(tasks);
MessageBox.Show(String.Join(Environment.NewLine, tasks.Select(t => t.Result.ToString())));
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
private async Task<Int32> GetContent(Int32 id)
{
using (var httpClient = new HttpClient())
{
var url = "http://localhost/TestService/api/test";
using (var responseMessage = await httpClient.GetAsync(url).ConfigureAwait(false))
{
// just read everything and return length
// ReadAsByteArrayAsync throws sometimes an exception
var content = await responseMessage.Content.ReadAsByteArrayAsync();
return content.Length;
}
}
}
我对实际流量很好奇,所以我设置了 Fiddler。当错误发生时,Fiddler 显示响应确实已损坏,并且实际上只发送了假定数据量的一部分(6MB、20MB,...而不是 34MB)。好像它被随机打断了。我用 Wireshark 玩了一会儿,我看到 RST/ACK 数据包是从服务器发送的,但我分析这种低级通信还不够好。
所以,我专注于服务器端。当然,我仔细检查了控制器的方法是否有任何异常。一切正常。我将日志级别设置为跟踪并在日志中找到以下内容:
info: Microsoft.AspNetCore.Server.Kestrel[28]
Connection id "0HL89D9NUNEOQ", Request id "0HL89D9NUNEOQ:00000001": the connection was closed becuase the response was not read by the client at the specified minimum data rate.
dbug: Microsoft.AspNetCore.Server.Kestrel[10]
Connection id "0HL89D9NUNEOQ" disconnecting.
...
info: Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv[14]
Connection id "0HL89D9NUNEOQ" communication error.
Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networking.UvException: Error -4081 ECANCELED operation canceled
我没有发现任何有趣的东西,ASP.NET 核心特定于此错误。根据 this documentation,IIS 有一个选项可以指定最小吞吐率,当它向客户端发送响应时,设置如下:
<system.applicationHost>
<webLimits minBytesPerSecond="0"/>
</system.applicationHost>
我在我的 Web.config
中使用它,但它没有任何效果(它应用于 ASP.NET 核心应用程序还是仅完整框架设置?)。
我尝试 return FileStreamResult
而不是 FileContentResult
,但还是没有用。
与客户端类似,我也试图为服务器端找到最少的可重现代码。方法只有 Thread.Sleep(8000)
(而不是数据库调用),然后生成随机的 50Mb 字节数组并 return 编辑它。这没有任何问题,所以我想我会继续朝那个方向调查。我知道数据库可能是这里的瓶颈,但不确定它是如何导致这种情况的(没有超时异常,没有死锁,...)。
有什么建议吗?我至少想知道它是服务器还是客户端相关问题。
您的吞吐量似乎低于最低数据速率。 Kestrel Fundamentals:
中描述了此行为Kestrel checks every second if data is coming in at the specified rate in bytes/second. If the rate drops below the minimum, the connection is timed out. The grace period is the amount of time that Kestrel gives the client to increase its send rate up to the minimum; the rate is not checked during that time. The grace period helps avoid dropping connections that are initially sending data at a slow rate due to TCP slow-start.
The default minimum rate is 240 bytes/second, with a 5 second grace period.
A minimum rate also applies to the response. The code to set the request limit and the response limit is the same except for having
RequestBody
orResponse
in the property and interface names.
您可以在 Program.cs 中这样配置:
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.Limits.MinResponseDataRate = null;
})
将此选项设置为 null
表示不应强制执行最低数据速率。