HttpClient和ASP.NET Core 2.0 Web服务之间的连接已关闭错误
我有一个在IIS上运行的ASP.NET Core 2.0 Web服务。 控制器的一种方法或多或少看起来像这样:
[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作为序列化程序。
然后我有客户端应用程序与此Web服务进行通信。 它是.NET Standard 2.0库,从.NET 4.6.1控制台应用程序引用。 我使用HttpClient
进行请求,并使用HttpResponseMessage.Content.ReadAsByteArrayAsync()
来读取响应(具体代码见下文)。
我想做一些测试。 我的桌子有cca。 80列,包含cca。 140000条记录。 所有这些都应该发送给客户。 从db获取数据需要几秒钟,然后它是序列化的所有内容和cca的结果。 34MB被发送给客户。
我有10个客户。 当他们连续调用webservice时,一切正常。 当我强调web服务和并行启动客户端时,我几乎总是会遇到一些错误(通常有一两个失败,有时甚至是4-5)。
以下是exception,它是从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.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.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) ...
我发现了几个与这种exception相关的SO线程(例如这里 ),所以我最初认为这是一个与客户端相关的问题。 答案建议:
- 切换到HTTP 1.0
- 设置
Connection: close
而不是Connection: keep-alive
- 反之亦然
没有什么对我有用。 我想我在某处看到HttpClient中有一些错误(现在找不到源代码)。 我试图使用Nuget的最新System.Net.Http
包。 同样的问题。 我创建了.NET Core控制台应用程序并使用了HttpClient
Core版本。 同样的问题。 我使用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 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数据包是从服务器发送的,但我对分析这种低级通信还不够好。
所以,我专注于服务器端。 当然,我仔细检查控制器的方法是否有任何exception。 一切正常。 我将日志级别设置为跟踪,并在日志中找到以下内容:
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 Core特定的相关的错误。 根据此文档 ,IIS可以选择在向客户端发送响应时指定最小吞吐率,具有以下设置:
我在我的Web.config
使用它,但它没有效果(它是应用于ASP.NET核心应用程序还是仅仅是完整的框架设置?)。
我试图返回FileStreamResult
而不是FileContentResult
,但是再次 – 它没有帮助。
与客户端类似,我也尝试为服务器端找到最小的可重现代码。 方法只有Thread.Sleep(8000)
(而不是db调用),然后生成随机50Mb字节数组并返回它。 这没有任何问题,所以我想我会继续调查这个方向。 我知道db可能是这里的瓶颈,但不确定它是如何导致这种情况的(没有超时exception,没有死锁,……)。
有什么建议吗? 我至少想知道它是服务器还是与客户端相关的问题。
看起来您的吞吐量低于最低数据速率。 Kestrel Fundamentals中描述了此行为:
如果数据以指定的速率(以字节/秒为单位)进入,则Kestrel会每秒检查一次。 如果速率低于最小值,则连接超时。 宽限期是Kestrel给客户端将发送速率提高到最小的时间; 在此期间不检查费率。 宽限期有助于避免丢弃由于TCP慢启动而最初以低速率发送数据的连接。
默认最小速率为240字节/秒,宽限期为5秒。
最低费率也适用于回复。 除了在属性和接口名称中
RequestBody
或Response
之外,设置请求限制和响应限制的代码是相同的。
您可以在Program.cs中配置它,如下所示:
var host = new WebHostBuilder() .UseKestrel(options => { options.Limits.MinResponseDataRate = null; })
将此选项设置为null
表示不应强制实施最低数据速率。