我在ASP.NET上运行了一个IISCore2.0webservice。控制器的方法之一大致如下所示:
[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");
}
它执行一些数据库更新、从表中查询记录、序列化数据并将其作为响应发送。数据以二进制格式发送。我使用MessagePack-CSharp作为序列化程序。
然后,我有客户端应用程序与此with服务进行通信。它是.NET标准2.0库,它从.NET 4.6.1控制台应用程序中引用。我使用HttpClient
来请求和HttpResponseMessage.Content.ReadAsByteArrayAsync()
来读取响应(确切的代码见下面)。
我想做些测试。我的桌子上有cca。80列,包含cca。140000张唱片。所有这些都应该发送给客户。从db获取数据只需几秒钟,那么它就是所有的序列化和cca的结果。34 is被发送到客户端。
我有10个客户。当他们连续调用webservice时,一切都正常。当我强调webservice并并行解雇客户端时,我几乎总是会得到其中一些错误(通常是一个或两个失败,有时甚至是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)
...
我发现了几个与这种异常相关的线程(例如这里),所以我最初认为这是一个与客户端相关的问题。答复建议:
Connection: close
而不是Connection: keep-alive
对我没什么用。我想我在某个地方读到了HttpClient中有一些bug (现在找不到源代码)。我试着使用来自Nuget的最新System.Net.Http
软件包。同样的问题。我创建了.NET核心控制台应用程序,并使用了HttpClient
的核心版本。同样的问题。我用的是HttpWebRequest
而不是HttpClient
。同样的潜在问题。
我在同一台VM机器上运行webservice和客户端。为了排除一些本地问题,我从其他计算机同时运行客户端。同样的问题。
因此,我得到了以下简化代码(只有一个有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显示响应确实已损坏,并且实际上只发送了一部分假定的数据量(6MB,20 6MB,.而不是34 of )。好像是随机中断的。我和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核心与这个错误有关。根据这份文件,IIS在向客户端发送响应时可以选择指定最低吞吐量,设置如下:
<system.applicationHost>
<webLimits minBytesPerSecond="0"/>
</system.applicationHost>
我在我的Web.config
中使用它,但它没有效果(它是应用于ASP.NET核心应用程序,还是只设置完整的框架?)
我试图返回FileStreamResult
而不是FileContentResult
,但是--这并没有帮助。
与客户端类似,我也试图为服务器端找到最小的可重复代码。方法只有Thread.Sleep(8000)
(而不是db调用),然后生成随机的50 db字节数组并返回它。这没什么问题,所以我想我会继续朝这个方向调查。我知道db可能是这里的瓶颈,但不确定它如何导致这种情况(没有超时异常,没有死锁,.)。
有什么建议吗?我至少想知道这是服务器问题还是真正与客户有关的问题。
发布于 2017-10-05 03:58:14
看起来,您的吞吐量低于最低数据速率。此行为在Kestrel基本面中描述。
Kestrel每秒钟检查一次数据是否以字节/秒的指定速率输入。如果速率低于最小值,则连接将超时。宽限期是Kestrel给予客户将其发送速率提高到最低限度的时间;在此期间不检查该费率。宽限期有助于避免由于TCP慢启动而导致最初以慢速率发送数据的连接中断。 默认的最小速率为240字节/秒,宽限期为5秒。 最低费率也适用于答复。设置请求限制和响应限制的代码是相同的,除非在属性和接口名称中有
RequestBody
或Response
。
您可以在Program.cs中像这样配置它:
var host = new WebHostBuilder()
.UseKestrel(options =>
{
options.Limits.MinResponseDataRate = null;
})
将此选项设置为null
表示不应强制执行最低数据速率。
https://stackoverflow.com/questions/46584760
复制