вместо MinResponseDataRate

я есть веб-сервис ASP.NET Core 2.0, работающий на IIS. Один из методов контроллера выглядит примерно так:

[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 в качестве сериализатора.

Тогда у меня есть клиентское приложение, которое связывается с этим веб-сервисом. Это библиотека .NET Standard 2.0, на которую ссылается консольное приложение .NET 4.6.1. я используюHttpClient для запроса иHttpResponseMessage.Content.ReadAsByteArrayAsync() прочитать ответ (точный код см. ниже).

Я хотел сделать несколько тестов. Мой стол имеет CCA. 80 столбцов и содержит около. 140000 записей. Все они должны быть отправлены клиенту. Получение данных из БД занимает несколько секунд, затем все сериализуется и результат cca. 34 МБ отправлено клиенту.

У меня 10 клиентов. Когда они вызывают веб-сервис поочередно, все работает. Когда я делаю ударение на веб-сервисе и запускаю клиентов параллельно, я почти всегда получаю сообщение об ошибке на некоторых из них (обычно один или два сбоя, иногда даже 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 была какая-то ошибка (сейчас не могу найти источник). Я пытался использовать новейшиеSystem.Net.Http пакет от Nuget. Та же проблема. Я создал консольное приложение .NET Core и использую версию CoreHttpClient, Та же проблема. я использовал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 показывает, что ответ действительно поврежден и фактически была отправлена ​​только часть предполагаемого объема данных (6 МБ, 20 МБ, ... вместо 34 МБ). Похоже, это прерывается случайно. Я играл некоторое время с 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, связанного с этой ошибкой. Согласно сэта документацияIIS имеет возможность указать минимальную пропускную способность при отправке ответа клиенту со следующими настройками:

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

Я использую это в моемWeb.config, но это не имеет никакого эффекта (это применимо к приложениям ASP.NET Core или это настройка только для полного фреймворка?).

Я пытался вернутьсяFileStreamResult вместоFileContentResult, но опять же - это не помогло.

Подобно клиенту, я попытался найти минимальный воспроизводимый код и для серверной части. Метод только что былThread.Sleep(8000) (вместо вызова db), затем сгенерировал случайный байтовый массив размером 50 Мб и вернул его. Это работало без каких-либо проблем, поэтому я думаю, что я продолжу расследование в этом направлении. Я знаю, что db может быть узким местом здесь, но не уверен, как это может вызвать это (нет исключения тайм-аута, нет тупика, ...).

Любые советы? Я хотел бы по крайней мере знать, является ли это проблемой, связанной с сервером или клиентом.

Ответы на вопрос(1)

Решение Вопроса

ваша пропускная способность падает ниже минимальной скорости передачи данных. Это поведение описано вОсновы пустельги:

Kestrel проверяет каждую секунду, поступают ли данные с указанной скоростью в байтах / секунду. Если скорость падает ниже минимальной, время соединения истекло. Льготный период - это время, которое Kestrel предоставляет клиенту для увеличения скорости отправки до минимума; ставка не проверяется в течение этого времени. Льготный период помогает избежать разрыва соединений, которые первоначально отправляют данные с низкой скоростью из-за медленного запуска TCP.

Минимальная скорость по умолчанию составляет 240 байт / с с 5-секундным льготным периодом.

Минимальная ставка также применяется к ответу. Код для установки лимита запроса и лимита ответа одинаков за исключением того, чтоRequestBody или жеResponse в имени свойства и интерфейса.

Вы можете настроить это в Program.cs следующим образом:

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

Установка этой опции наnull указывает на отсутствие минимальной скорости передачи данных.

 halter7317 нояб. 2017 г., 21:26
@daniherculano Похоже, вы бьетеMinRequestBodyDataRate вместо MinResponseDataRate
 daniherculano10 нояб. 2017 г., 14:05
Та же проблема моя. У меня есть Java-клиент и NetCore 2 Server, и с тех пор, как я обновился до NetCore 2, мы испытали случайные проблемы в некоторых соединениях между клиентом и сервером, включая загрузку файлов. огромное спасибо
 Stalker05 окт. 2017 г., 21:41
Спасибо большое, похоже, это работает. Но что продолжает беспокоить меня, так это то, почему это происходит. Я предполагаю, что количество передаваемых данных не так велико, чтобы заставить клиентов изо всех сил пытаться обработать ответ. Кроме того, игнорирование этого ограничения делает службу уязвимой для Slow Client Attack, я полагаю.
 daniherculano13 нояб. 2017 г., 21:05
Привет снова @Knelis, после твоего решения я мог каждый раз загружать файлы своим клиентам, но у меня уже иногда есть другое сообщение в моем журнале, и мой параметр действия равен нулю, несмотря на настройкуMinResponseDataRate к нулю. Это говорит:истекло время ожидания запроса, поскольку оно не было отправлено клиентом со скоростью как минимум 240 байт / с

Ваш ответ на вопрос