Подтвердить что ты не робот

Сложность использования WCF для загрузки больших файлов

Есть много других подобных вопросов по SO об этом. К сожалению, многие, похоже, по очереди обманывают друг друга. Надеюсь, что этот поможет другим и поставит другие вопросы.

Мое требование к проекту заключается в том, чтобы загружать файлы 250 МБ через IIS в серверную службу WCF, размещенную в IIS. Я создал некоторые модульные тесты для бэкэнда WCF-сервиса, размещенного в IIS. Это:

1) Upload 1MB File
2) Upload 5MB File
3) Upload 10MB file
4) Upload 20MB File
5) Upload 200MB File

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

В примере описывается метод, который использует объект .NET Stream. Побочным эффектом использования объекта потока является то, что вы должны использовать Контракты сообщений. Этого недостаточно, чтобы поместить Stream в список параметров вашей функции. Поэтому мы делаем это.

По умолчанию web.config для этой службы WCF довольно скудный. И ничего не работает:

System.ServiceModel.ProtocolException: The remote server returned an unexpected response: (400) Bad Request. ---> System.Net.WebException: The remote server returned an error: (400) Bad Request.

После долгих поисков и экспериментов ясно, что BasicHttpBinding несовместимо с этой комбинацией объекта Stream и MessageContract. Мы должны переключиться на WSHttpBinding.

Для этого сервер web.config становится немного более сложным в разделе:

<system.serviceModel>
        <behaviors>
            <serviceBehaviors>
                <behavior>
                    <!-- To avoid disclosing metadata information, set the value below to false and remove the metadata endpoint above before deployment -->
                    <serviceMetadata httpGetEnabled="true"/>
                    <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
                    <serviceDebug includeExceptionDetailInFaults="true" httpHelpPageEnabled="true"/>
                </behavior>
                <behavior name="FileServiceBehavior">
                    <serviceMetadata httpGetEnabled="true"/>
                    <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
                    <serviceDebug includeExceptionDetailInFaults="true"/>
                    <serviceThrottling maxConcurrentCalls="500" maxConcurrentSessions="500" maxConcurrentInstances="500"/>
                </behavior>
            </serviceBehaviors>
        </behaviors>
        <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
        <bindings>
            <wsHttpBinding>
                <binding name="FileServiceBinding" closeTimeout="10:01:00"
                  maxBufferPoolSize="104857600"
                  maxReceivedMessageSize="104857600" openTimeout="10:01:00"
                  receiveTimeout="10:10:00" sendTimeout="10:01:00"
                  messageEncoding="Mtom">
                    <readerQuotas maxDepth="104857600" maxStringContentLength="104857600"
                                  maxArrayLength="104857600" maxBytesPerRead="104857600"
                                  maxNameTableCharCount="104857600" />
                </binding>
            </wsHttpBinding>
        </bindings>
        <services>
            <service behaviorConfiguration="FileServiceBehavior" name="OMS.Service.FileService">
                <endpoint address="" binding="wsHttpBinding" bindingConfiguration="FileServiceBinding" contract="OMS.Service.IFileService"></endpoint>
            </service>
        </services>
    </system.serviceModel>

Без какой-либо дополнительной работы, 1MB файл теперь проходит без проблем.

Чтобы получить файлы размером более 4 МБ, вы должны настроить параметр в файле web.config в IIS (на стороне сервера вашей службы WCF) В этой статье от Microsoft объясняется, что это за настройка. Например, если вы установите ее на 8192, вы сможете загрузить файл 5 МБ, но не больше.

<httpRuntime maxRequestLength="8192" />

Я установил для себя что-то непристойное для тестирования - 2147483647. Первые 4 файла передают эти ворота.

200MB не получил шанс добраться до этих ворот по следующей причине:

System.InsufficientMemoryException: Failed to allocate a managed memory buffer of 279620368 bytes. The amount of available memory may be low. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.

Объяснение этой проблемы описано очень хорошо, на этом плакате.

Подумайте об этом так. Файл 200 МБ никогда не делал его из клиента. Он должен быть полностью загружен клиентом, зашифрован и затем передан на сервер.

Когда вы используете Visual Studio 2010 для создания прокси-классов для службы, она помещает некоторые вещи в ваш app.config. Для меня это выглядит так:

<binding 
 name="Binding_IFileService" closeTimeout="00:01:00"
 openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
 bypassProxyOnLocal="false" transactionFlow="false" 
 hostNameComparisonMode="StrongWildcard"
 maxBufferPoolSize="524288" maxReceivedMessageSize="65536" messageEncoding="Mtom"
 textEncoding="utf-8" useDefaultWebProxy="true" allowCookies="false">
    <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
     maxBytesPerRead="4096" maxNameTableCharCount="16384" />
    <reliableSession ordered="true" inactivityTimeout="00:10:00"
         enabled="false" />
    <security mode="Message">
        <transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
        <message clientCredentialType="Windows" negotiateServiceCredential="true" />
    </security>
</binding>

Ключ - это режим безопасности. По умолчанию установлено сообщение. Это значение выбирается тем, что установлено на сервере. По умолчанию ваш сервер использует защиту уровня сообщений.

Если вы попытаетесь заставить его на сервере выглядеть так:

 <security mode="None">

Вы получите эту ошибку:

System.ServiceModel.EndpointNotFoundException: There was no endpoint listening at http://localhost:8080/oms/FileService.svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details. ---> System.Net.WebException: The remote server returned an error: (404) Not Found.

(я не забыл обновить клиентский прокси)

И так, что там, где это для меня.... Помогите!

4b9b3361

Ответ 1

WCF обычно не обрабатывает большие передачи файлов хорошо, если вы не реализуете потоковое вещание, которое может быть выполнено с помощью BasicHttpBindings.

Для моего проекта у меня есть собственный хост factory, который создает хосты служб:

protected override System.ServiceModel.ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
{
    ServiceHost host = new ServiceHost(serviceType, baseAddresses);

    ContractDescription contract = ContractDescription.GetContract(serviceType);

    BasicHttpBinding binding = new BasicHttpBinding();
    binding.OpenTimeout = TimeSpan.FromMinutes(1);
    binding.ReceiveTimeout = TimeSpan.FromMinutes(1);
    binding.SendTimeout = TimeSpan.FromHours(1);
    binding.TransferMode = TransferMode.StreamedResponse;
    binding.MessageEncoding = WSMessageEncoding.Mtom;

    ServiceEndpoint streaming = new ServiceEndpoint(contract, binding, new EndpointAddress(baseAddresses[0] + "/STREAMING"));

    host.AddServiceEndpoint(streaming);

    return host;
}

Вам понадобится использовать StreamedRequest в вашем случае.

И реализация StreamingService:

[ServiceBehavior(ConcurrencyMode = ConcurrencyMode.Multiple, InstanceContextMode = InstanceContextMode.Single, AddressFilterMode = AddressFilterMode.Any)]
public class FileStreamingService : IFileStreamingV1
{
    Stream IFileStreamingV1.GetFileStream(string downloadFileLocation)
    {
        if (!File.Exists(downloadFileLocation))
        {
            throw new FaultException("The file could not be found");
        }

        FileStream stream = File.OpenRead(downloadFileLocation);
        return stream;
    }
}

Я не указывал максимальный размер буфера в самой службе, но клиентское приложение в моем случае дросселировало загрузку на что-то вроде 5 Мб в секунду. В вашем случае самой службе нужно будет установить поведение дроссельной заслонки. Это не решит проблему того, как вы сообщите службе, сколько байтов в файле, чтобы правильно ее перевести, но это должно дать вам начало.

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

У меня нет примера поведения клиента, но ваша служба должна прочитать количество байтов из буфера потока загрузки и сохранить их в файл, пока ничего не останется для потока. Хотя память дешевая, я бы не рекомендовал хранить полную копию в памяти файла, особенно в 200 мб.

Вы также должны знать, что в зависимости от вашей платформы веб-хостинга (IIS, Apache и т.д.) вы также можете ограничить объем данных, которые могут быть переданы в данный момент времени. Однако изменение конфигурации обычно может разрешать любые проблемы с хостингом.

Надеюсь, это поможет.

Ответ 2

Хороший подробный вопрос:)

Последнее сообщение об ошибке было 404 файла не найдено. Обычно это то, что файл отсутствует или что сайт не работает. Проверьте это, чтобы это исключить.

  • Попробуйте просмотреть файл svc
  • Проверьте, что он по-прежнему работает с меньшим файлом.

На основе последнего изменения вы отключили шифрование на основе обмена сообщениями. Это изменение должно быть сделано как на клиенте, так и на сервере. Если одна сторона шифрует, а другая не ожидает, что сообщение будет зашифровано, оно запутается.

Ответ 3

Можно сжать файлы?, я знаю, что это не решение, но оно может помочь, если вы знаете максимальный размер, который будет поддерживаться.

Если не единственный способ потоковой передачи файла или разделен на несколько частей.