Загрузка файлов через WCF медленнее, чем через IIS - PullRequest
10 голосов
/ 16 января 2009

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

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

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

Спасибо.

Ответы [ 6 ]

31 голосов
/ 09 сентября 2009

После 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, это количество времени, которое отводится на заполнение буфера соединения до того, как платформа выдаст исключение ввода-вывода. Поскольку мы увеличили размер буфера, мы также увеличили время, отведенное на заполнение буфера.

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

3 голосов
/ 17 января 2009

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

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

2 голосов
/ 07 сентября 2017

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

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

        transport?.GetType().GetProperty("ConnectionBufferSize", BindingFlags.Public | BindingFlags.Instance)?.SetValue(transport, 256192);
0 голосов
/ 19 апреля 2019

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

Большинство ответов здесь указывают на то, что люди используют одну конечную точку 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) Очевидно, это не проблема, если вы самостоятельно размещаете веб-сервис, но я не проверил это. Поэтому, если вы добавили customBinding для одного 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

0 голосов
/ 26 апреля 2018

Ответ, основанный на рефлексии, работает для нас. Если вам нужно сделать это через размещенную службу 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 });  
}  
0 голосов
/ 28 ноября 2017

Вот еще один способ сделать это, не имея дело с отражением. Просто оберните 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...