Загрузка файла через WCF медленнее, чем через IIS

Следующий метод (в котором я надеюсь, что не делал ошибок при его существенном упрощении для этого сообщения) работает правильно и настроен с использованием режима передачи потоковой передачи по протоколу net.tcp. Проблема в том, что производительность значительно хуже, чем загрузка одного и того же файла через IIS через http. Почему это так, и что я могу изменить для повышения производительности?

Stream WebSiteStreamedServiceContract.DownloadFile( string filePath ) {
    return File.OpenRead( filePath );
}

Наконец, является ли WCF ответственным за правильное удаление моего потока, и хорошо ли он справляется с этим? Если нет, что я должен делать вместо этого?

Спасибо.

Ответ 1

После 8 месяцев работы над этой проблемой, 3 из них с Microsoft, вот решение. Короткий ответ заключается в том, что на стороне сервера (стороне, отправляющей большой файл) необходимо использовать следующее для привязки:

 <customBinding>
  <binding name="custom_tcp">
   <binaryMessageEncoding />
   <tcpTransport connectionBufferSize="256192" maxOutputDelay="00:00:30" transferMode="Streamed">
   </tcpTransport>
  </binding>
 </customBinding>

Ключевым здесь является атрибут connectionBufferSize. Может потребоваться установить несколько других атрибутов (maxReceivedMessageSize и т.д.), Но connectionBufferSize был виновником.

Никакой код не должен быть изменен на стороне сервера.

Никакой код не должен быть изменен на стороне клиента.

Никакая конфигурация не должна быть изменена на стороне клиента.

Вот длинный ответ:

Я подозревал, что причина, по которой net.tcp в WCF была медленной, заключалась в том, что она часто отправляла небольшие фрагменты информации, а не более крупные фрагменты информации, и это приводило к плохому выполнению работы в сетях с высокой задержкой ( интернет). Это оказалось правдой, но это был долгий путь, чтобы добраться туда.

В netTcpBinding есть несколько атрибутов, которые кажутся многообещающими: maxBufferSize является наиболее очевидным, а maxBytesPerRead и другие - также надежды. В дополнение к этим возможно создавать более сложные потоки, чем в исходном вопросе, - вы также можете указать размер буфера - как на стороне клиента, так и на стороне сервера. Проблема в том, что ничто из этого не имеет никакого влияния. Когда вы используете netTcpBinding, вы будете закрыты.

Причиной этого является то, что настройка maxBufferSize в netTcpBinding настраивает буфер на уровне протокола. Но ничего, что вы можете сделать для netTcpBinding, никогда не изменит базовый транспортный уровень. Вот почему мы так долго не смогли добиться успеха.

Пользовательская привязка решает проблему, потому что увеличение connectionBufferSize на транспортном уровне увеличивает количество информации, отправленной сразу, и, следовательно, передача намного менее подвержена задержке.

При решении этой проблемы я заметил, что maxBufferSize и maxBytesPerRead имели влияние производительности на сети с низкой задержкой (и локально). Microsoft сообщает мне, что maxBufferSize и connectionBufferSize независимы и что все комбинации их значений (равные друг другу, maxBufferSize больше, чем connectionBufferSize, maxBufferSize меньше, чем connectionBufferSize) действительны. У нас есть успех с maxBufferSize и maxBytesPerRead из 65536 байт. Опять же, это очень мало повлияло на производительность сети с высокой задержкой (исходная проблема).

Если вам интересно, что такое maxOutputDelay, то это количество времени, которое выделяется для заполнения буфера подключения до того, как среда генерирует исключение IO. Поскольку мы увеличили размер буфера, мы также увеличили время, затраченное на заполнение буфера.

Благодаря этому решению наша производительность увеличилась примерно на 400% и теперь немного лучше, чем IIS. Есть несколько других факторов, которые влияют на относительную и абсолютную производительность по сравнению с IIS через HTTP и WCF по сравнению с net.tcp(и WCF над http, если на то пошло), но это был наш опыт.

Ответ 2

Я не знаю ответа на ваш первый вопрос (я думаю, вам нужно предоставить больше кода, чтобы показать, что вы делаете для обоих тестов), но ваш второй вопрос об утилизации потока, ответ в том, что вам нужно сделать это самостоятельно.

Вот отличная запись в блоге, которая имеет отличный код для этой цели.

Ответ 3

Я принял @Greg-Smalter советы и изменения ConnectionBufferSize on NetTcpBinding, используя отражение, и это решило мою проблему. Потоки больших документов быстро кричат. Вот код.

        var transport = binding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
            ?.GetValue(binding);

        transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);

Ответ 4

Вот еще один способ сделать это, не имея дело с Reflection. Просто заверните NetTcpBinding в CustomBinding.

var binding = new CustomBinding(new NetTcpBinding
{
     MaxReceivedMessageSize = 2147483647,
     MaxBufferSize = 2147483647,
     MaxBufferPoolSize = 2147483647,
     ReceiveTimeout = new TimeSpan(4, 1, 0),
     OpenTimeout = new TimeSpan(4, 1, 0),
     SendTimeout = new TimeSpan(4, 1, 0),
     CloseTimeout = new TimeSpan(4, 1, 0),
     ReaderQuotas = XmlDictionaryReaderQuotas.Max,
     Security =
            {
                Mode = SecurityMode.None,
                Transport = {ClientCredentialType = TcpClientCredentialType.None}
            },
            TransferMode = TransferMode.Streamed,
            HostNameComparisonMode = HostNameComparisonMode.StrongWildcard
        });

binding.Elements.Find<TcpTransportBindingElement>().ConnectionBufferSize = 665600;

Ответ 5

Ответ, основанный на размышлениях, действительно работает для нас. Если вам нужно сделать это через размещенную службу IIS/WCF; Вы можете использовать волшебное объявление функции Configure, чтобы получить доступ к Binding, чтобы сделать это:

public static void Configure(ServiceConfiguration config)  
{  
    NetTcpBinding tcpBinding = new NetTcpBinding { /* Configure here */ };

    var transport = tcpBinding.GetType().GetField("transport", BindingFlags.NonPublic | BindingFlags.Instance)
            ?.GetValue(tcpBinding);
    transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);

    ServiceEndpoint se = new ServiceEndpoint(ContractDescription.GetContract(typeof(IService)), tcpBinding , new EndpointAddress("net.tcp://uri.foo/bar.svc"))
    {
        ListenUri = new Uri("net.tcp://uri.foo/bar.svc")
    };

    config.AddServiceEndpoint(se);

    config.Description.Behaviors.Add(new ServiceMetadataBehavior { HttpGetEnabled = true });  
    config.Description.Behaviors.Add(new ServiceDebugBehavior { IncludeExceptionDetailInFaults = true });  
}  

Ответ 6

Спасибо за информацию в этом посте. Там не так много информации об этой проблеме. Я хочу добавить некоторые дополнительные детали, которые могут быть полезны для других.

Кажется, что большинство ответов здесь указывают на то, что люди используют одну конечную точку NetTcp или они не размещают WCF внутри IIS.

Если вы используете несколько конечных точек netTcp в одной и той же службе wcf, размещенной в IIS, или контейнер, использующий WAS, вы можете столкнуться с этими проблемами.

  1. Если вы измените ConnectionBufferSize для одной конечной точки NetTcp, все они должны быть изменены, и они должны иметь одинаковое значение. Очевидно, это требование при размещении в WAS (именно это использует IIS). (https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-host-a-wcf-service-in-was) Очевидно, это не проблема, если вы сами -хостинг веб-службы, но я не проверил это. Поэтому, если вы добавили настраиваемую привязку для одного NetTcp, но получаете исключение активации из-за отсутствия TransportManager, это ваша проблема.
  2. Я не мог заставить это работать, используя магический метод Configure. Даже с базовой связкой NetTcp без особых изменений я получал нулевой объект ответа. Я уверен, что есть способ это исправить, но у меня не было времени продолжать копаться в этом.
  3. Способ, который работал для меня, состоял в том, чтобы использовать customBinding. Если вы используете транспортную безопасность с аутентификацией Windows, вы можете просто добавить <windowsStreamSecurity/> чтобы привязка в итоге выглядела так:
<binding name="CustomTcpBinding_ServerModelStreamed">
     <windowsStreamSecurity />
     <binaryMessageEncoding />
     <tcpTransport connectionBufferSize="5242880" maxReceivedMessageSize="2147483647" maxBufferSize ="2147483647" transferMode="Streamed" />
</binding>

Вам не нужно менять конфигурацию клиента NetTcp, конечно, если вы не используете никаких дополнительных функций, которые здесь не отражены. Для customBinding важен порядок разделов. https://docs.microsoft.com/en-us/dotnet/framework/wcf/extending/custom-bindings