【问题标题】:Connection was closed error between HttpClient and ASP.NET Core 2.0 webserviceHttpClient 和 ASP.NET Core 2.0 Web 服务之间的连接已关闭错误
【发布时间】:2018-03-17 00:31:24
【问题描述】:

我有一个在 IIS 上运行的 ASP.NET Core 2.0 网络服务。控制器的方法之一看起来或多或少是这样的:

[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 4.6.1 控制台应用程序引用的 .NET Standard 2.0 库。我使用HttpClient 进行请求,使用HttpResponseMessage.Content.ReadAsByteArrayAsync() 读取响应(具体代码见下文)。

我想做一些测试。我的桌子有 cca​​。 80 列,包含 cca。 140000 条记录。所有这些都应该发送给客户。从 db 获取数据需要几秒钟,然后它是所有的序列化和 cca 的结果。 34MB 被发送到客户端。

我有 10 个客户。当他们连续调用 web 服务时,一切正常。当我同时强调 web 服务和触发客户端时,我几乎总是在其中一些上遇到错误(通常一两个失败,有时甚至 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 线程(例如 here),所以我最初认为这是与客户端相关的问题。建议的答案:

  • 切换到 HTTP 1.0
  • 设置Connection: close 而不是Connection: keep-alive
  • 反之亦然

没有什么对我有用。我想我在某处读到 HttpClient 中有一些错误(现在找不到源)。我尝试使用来自 Nuget 的最新 System.Net.Http 包。同样的问题。我创建了 .NET Core 控制台应用程序并使用 HttpClient 的核心版本。同样的问题。我使用HttpWebRequest 而不是HttpClient。同样的根本问题。

我在同一台虚拟机上运行 web 服务和客户端。为了排除一些本地问题,我同时从其他计算机运行客户端。同样的问题。

所以我最终得到了以下简化代码(只有一个具有 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 Core 的内容。根据this documentation,IIS 在向客户端发送响应时可以选择指定最小吞吐率,设置如下:

<system.applicationHost>
  <webLimits minBytesPerSecond="0"/>
</system.applicationHost>

我在我的 Web.config 中使用它,但它没有效果(它是应用于 ASP.NET Core 应用程序还是仅用于完整框架设置?)。

我尝试返回 FileStreamResult 而不是 FileContentResult,但又一次 - 它没有帮助。

与客户端类似,我也尝试为服务器端找到最少的可重现代码。方法只有Thread.Sleep(8000)(而不是db调用),然后生成随机50Mb字节数组并返回它。这没有任何问题,所以我想我会继续朝这个方向进行调查。我知道 db 可能是这里的瓶颈,但不确定它是如何导致这种情况的(没有超时异常,没有死锁,......)。

有什么建议吗?我至少想知道它是服务器还是客户端相关的问题。

【问题讨论】:

    标签: c# .net iis dotnet-httpclient asp.net-core-2.0


    【解决方案1】:

    您的吞吐量似乎低于最低数据速率。此行为在Kestrel Fundamentals 中进行了描述:

    Kestrel 每秒检查一次数据是否以指定的字节/秒速率进入。如果速率低于最小值,则连接超时。宽限期是 Kestrel 给予客户端将其发送速率提高到最小值的时间量;在此期间不检查费率。宽限期有助于避免由于 TCP 慢启动而丢弃最初以慢速发送数据的连接。

    默认最小速率为 240 字节/秒,宽限期为 5 秒。

    最低费率也适用于响应。设置请求限制和响应限制的代码相同,只是属性和接口名称中有RequestBodyResponse

    您可以像这样在 Program.cs 中进行配置:

    var host = new WebHostBuilder() 
        .UseKestrel(options => 
        { 
            options.Limits.MinResponseDataRate = null;
        })
    

    将此选项设置为null 表示不应强制执行最低数据速率。

    【讨论】:

    • 非常感谢,这似乎有效。但一直困扰我的是为什么会发生这种情况。我想传输的数据量并没有那么大,以至于客户难以处理响应。此外,我猜,忽略此限制会使服务容易受到慢速客户端攻击。
    • 我的同样问题。我有一个 java 客户端和 NetCore 2 服务器,自从我更新到 NetCore 2,我们在客户端和服务器之间的一些连接中试验了随机问题,包括文件下载。非常感谢
    • 您好@Knelis,在您的解决方案之后,我每次都可以将文件下载到我的客户,但有时我的日志中已经有不同的消息并且我的操作参数为空,尽管将MinResponseDataRate 设置为空.它说:请求超时,因为它不是由客户端以至少 240 字节/秒的速度发送的
    • @daniherculano 看起来你正在点击 MinRequestBodyDataRate 而不是 MinResponseDataRate
    猜你喜欢
    • 1970-01-01
    • 2021-11-08
    • 1970-01-01
    • 2020-11-20
    • 2011-06-27
    • 2017-05-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多