2017-10-05 165 views
4

我有一个在IIS上运行的ASP.NET Core 2.0 webservice。其中一个控制器的方法看起来或多或少是这样的:HttpClient和ASP.NET Core 2.0之间的连接已关闭错误webservice

[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作为序列化程序。

然后我有客户端应用程序与此webservice通信。它是.NET Standard 2.0库,它是从.NET 4.6.1控制台应用程序引用的。我使用HttpClient请求和HttpResponseMessage.Content.ReadAsByteArrayAsync()读取响应(确切代码见下文)。

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

我有10个客户。当他们连续调用webservice时,一切正常。当我强调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控制台应用程序并使用了Core版本HttpClient。同样的问题。我用HttpWebRequest而不是HttpClient。相同的根本问题。

    我在同一台虚拟机上运行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。发生错误时,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 Core应用程序,还是仅限于完整框架设置?)。

    我试图返回FileStreamResult而不是FileContentResult,但再次 - 它没有帮助。

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

    任何建议?我至少想知道它是服务器还是真正与客户端相关的问题。

  • 回答

    2

    看起来您的吞吐量降至最低数据速率以下。此行为在Kestrel Fundamentals中描述:

    Kestrel每秒钟检查数据是否以指定的速率以字节/秒进入。如果速率低于最小值,则连接超时。宽限期是Kestrel让客户将发送速率提高到最低限度的时间;在此期间不检查费率。宽限期有助于避免由于TCP慢启动而导致最初以低速发送数据的连接中断。

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

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

    您可以在Program.cs中这样的配置这样的:

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

    这个选项设置为null表示没有最低数据速率应该强制执行。

    +0

    非常感谢,这似乎工作。但是困扰我的是为什么会发生这种情况。我认为传输的数据量并不那么大,以致客户难以处理响应。另外,我猜,忽略这个限制会使服务容易受到慢速客户端攻击。 – Stalker

    +0

    同样的问题我的。我有一个Java客户端和NetCore 2服务器,并且自从我更新到NetCore 2后,我们在客户端和服务器之间的某些连接(包括文件下载)中尝试了随机问题。非常感谢你 – daniherculano

    +0

    嗨@Knelis,在你的解决方案之后,我可以每次下载文件到我的客户端,但是我的日志中有时会有不同的消息,尽管将MinResponseDataRate设置为null,但我的动作参数为空。它说:_request超时,因为它不是由客户端以至少240字节/秒发送的_ – daniherculano

    相关问题