web-dev-qa-db-fra.com

WCF Streaming de données volumineuses (500 Mo / 1 Go) sur un service auto-hébergé

Je rencontre actuellement un problème lors de l'envoi de données volumineuses à l'aide du service auto-hébergé WCF (pas d'IIS). Transfert de 500 Mo en utilisant les résultats de la diffusion en continu avec un crash de mon service avec System.OutOfMemoryException. Est-il possible de transférer une telle quantité de données?

Voici ma configuration WCF:

<system.serviceModel>
<services>
  <service  name="CIService" behaviorConfiguration="CIBehavior">        
    <Host>
      <baseAddresses>
        <add baseAddress="net.tcp://localhost:6547/CIService/CIService.svc" />
      </baseAddresses>
    </Host>
    <endpoint binding="netTcpBinding" 
        bindingConfiguration="netTcpBindingConfig" 
        behaviorConfiguration="CIBehavior.EndpointBehavior" 
        contract="CIService.ICreatable" />
    <endpoint address="mex" 
        binding="mexHttpBinding" 
        name="mexTcpBinding" 
        contract="IMetadataExchange" />
  </service>
</services>
<serviceHostingEnvironment multippleSiteBindingEnabled="True" />
<bindings>
  <netTcpBinding>
    <binding name="netTcpBindingConfig" closeTimeout="00:01:00" openTimeout="00:01:00" 
        receiveTimeout="01:00:00" sendTimeout="00:10:00" 
        hostNameComparisonMode="StrongWildcard" listenBacklog="10" maxConnections="10"
        maxBufferSize="2147483647" maxBufferPoolSize="2147483647" maxReceivedMessageSize="2147483647" 
        transferMode="Streamed">
      <readerQuotas maxDepth="2147483647" maxStringContentLength="2147483647" maxArrayLength="2147483647"
        maxBytesPerRead="2147483647" maxNameTableCharCount="2147483647" />
    </binding>
  </netTcpBinding>
</bindings>

<behaviors>
  <serviceBehaviors>
    <behavior name="CIBehavior">
      <serviceMetadata httpGetEnabled="False" />
      <serviceDebug includeExceptionDetailInFaults="true" />
      <serviceThrottling maxConcurrentCalls="200"  maxConcurrentInstances="2147483647" maxConcurrentSessions="100" />
      <dataContractSerializer maxItemsInObjectGraph="2147483647"/>
    </behavior>
  </serviceBehaviors>
  <endpointBehavior>
    <behavior name="CIBehavior.EndpointBehavior">
      <dataContractSerializer maxItemsInObjectGraph="2147483647" />
    </behavior>
  </endpointBehavior>
</behaviors>
</system.serviceModel>

Ma configuration client:

<system.serviceModel>
<bindings>
  <netTcpBinding>
    <binding name="NetTcpBinding_ICreatable" 
             closeTimeout="00:01:00" openTimeout="00:01:00" 
             receiveTimeout="01:00:00" sendTimeout="00:10:00" 
             transactionFlow="false" 
             transferMode="Streamed" 
             transactionProtocol="OleTransactions" 
             hostNameComparisonMode="StrongWildcard" 
             listenBacklog="10" 
             maxBufferPoolSize="2147483647" 
             maxBufferSize="2147483647" 
             maxConnections="10"
             maxReceivedMessageSize ="2147483647">
      <readerQuotas
        maxDepth="2147483647" 
        maxStringContentLength="2147483647" 
        maxArrayLength="2147483647" 
        maxBytesPerRead="2147483647" 
        maxNameTableCharCount="2147483647" />
      <reliableSession ordered="true" inactivityTimeout="00:10:00" anabled="false" />
    </binding>
  </netTcpBinding>
</bindings>
<client>
  <endpoint name="NetTcpBinding_ICreatable" 
      address="net.tcp://localhost:6547/CIService/CIService.svc" 
      binding="netTcpBinding" 
      bindingConfiguration="NetTcpBinding_ICreatable" 
      behaviorConfiguration="CIBehavior.EndpointBehavior" 
      contract="ICreatable" />
</client>
<behaviors>
  <endpointBehavior>
    <behavior name="CIBehavior.EndpointBehavior">
      <dataContractSerializer maxItemsInObjectGraph="2147483647" />
    </behavior>
  </endpointBehavior>
</behaviors>
</system.serviceModel> 
20
user2003667

Vous n'avez pas besoin de maxBufferSize ou maxBufferPoolSize défini si haut, ce qui peut éventuellement causer votre exception de mémoire insuffisante. Les valeurs par défaut devraient être correctes.

Consultez Grandes données et streaming sur MSDN, en particulier la section Considérations spéciales de sécurité pour les grandes données cette partie du texte est importante

La propriété MaxBufferSize est requise pour contraindre la mémoire que les tampons WCF. Il est important de définir cette valeur sur une valeur sûre (ou de la conserver à la valeur par défaut) lors de la diffusion. Par exemple, supposons que votre service doit recevoir des fichiers jusqu'à 4 Go et les stocker sur le disque local. Supposons également que votre mémoire soit contrainte de manière à ce que vous ne puissiez mettre en mémoire tampon que 64 Ko de données à la fois. Vous devez ensuite définir MaxReceivedMessageSize sur 4 Go et MaxBufferSize sur 64 Ko. De plus, dans votre implémentation de service, vous devez vous assurer que vous lisez uniquement à partir du flux entrant en blocs de 64 Ko et que vous ne lisez pas le bloc suivant avant que le précédent n'ait été écrit sur le disque et supprimé de la mémoire.

J'ai rassemblé un exemple très simple de streaming de données d'un service auto-hébergé vers un client de console. Pour garder le message court, j'ai seulement ajouté le code du serveur et une partie du client.

Le contrat de service

using System.IO;
using System.ServiceModel;

namespace Service
{
    [ServiceContract]
    public interface IStream
    {
        [OperationContract]
        Stream GetLargeObject();
    }
}

La mise en œuvre du service

using System;
using System.IO;
using System.ServiceModel;

namespace Service
{
   [ServiceBehavior]
   public class StreamService : IStream
   {
       public Stream GetLargeObject()
       {
           // Add path to a big file, this one is 2.5 gb
           string filePath = Path.Combine(Environment.CurrentDirectory, "C:\\Temp\\BFBC2_PC_Client_R11_795745_Patch.exe");

        try
        {
            FileStream imageFile = File.OpenRead(filePath);
            return imageFile;
        }
        catch (IOException ex)
        {
            Console.WriteLine(String.Format("An exception was thrown while trying to open file {0}", filePath));
            Console.WriteLine("Exception is: ");
            Console.WriteLine(ex.ToString());
            throw;
        }
    }
 }
}

Le service principal

using System;
using System.ServiceModel;

namespace Service
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                using (var serviceHost = new ServiceHost(typeof(StreamService)))
                {
                    serviceHost.Open();

                    Console.WriteLine("Press Any Key to end");
                    Console.ReadKey();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
        }
    }
}

Le service app.config

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>

  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="StreamServiceBehavior">
          <serviceMetadata httpGetEnabled="True" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <netTcpBinding>
        <binding name="NewBinding0" transferMode="Streamed"/>
      </netTcpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="StreamServiceBehavior" name="Service.StreamService">
        <endpoint address="net.tcp://localhost:9000/streamserver" binding="netTcpBinding"
          bindingConfiguration="NewBinding0" bindingName="" contract="Service.IStream" />
        <endpoint address="mex" binding="mexHttpBinding"
          contract="IMetadataExchange" />
        <Host>
          <baseAddresses>
            <add baseAddress="http://localhost:8080/StreamService" />
          </baseAddresses>
        </Host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Lancez le service, vous devrez peut-être exécuter sous le compte administrateur pour ouvrir le socket. Créez une application de console client et ajoutez une référence de service à l'aide de l'url http: // localhost: 8080/StreamService, en utilisant Service comme espace de noms pour le client généré.

Le client principal

using System;
using System.IO;
using Client.Service;

namespace Client
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                using (StreamClient streamClient = new StreamClient())
                {
                    streamClient.Open();

                    using (FileStream fileStream = new FileStream("c:\\temp\\bigfile.exe",FileMode.Create))
                    {
                        streamClient.GetLargeObject().CopyTo(fileStream);    
                    }
                }

                Console.WriteLine("Press any key to end");
                Console.ReadKey();
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }
    }
}

Le fichier de configuration client généré devra être légèrement modifié, augmenter receiveTimeout et définir maxReceivedMessageSize = "4294967295"

<system.serviceModel>
    <bindings>
        <netTcpBinding>
            <binding name="NetTcpBinding_IStream" closeTimeout="00:01:00"
                openTimeout="00:01:00" receiveTimeout="00:30:00" sendTimeout="00:01:00"
                transactionFlow="false" transferMode="Streamed" transactionProtocol="OleTransactions"
                hostNameComparisonMode="StrongWildcard" listenBacklog="10"
                maxBufferPoolSize="524288" maxBufferSize="65536" maxConnections="10"
                maxReceivedMessageSize="4294967295">
                <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
                    maxBytesPerRead="4096" maxNameTableCharCount="16384" />
                <reliableSession ordered="true" inactivityTimeout="00:10:00"
                    enabled="false" />
                <security mode="Transport">
                    <transport clientCredentialType="Windows" protectionLevel="EncryptAndSign" />
                    <message clientCredentialType="Windows" />
                </security>
            </binding>
        </netTcpBinding>
    </bindings>
    <client>
        <endpoint address="net.tcp://localhost:9000/streamserver" binding="netTcpBinding"
            bindingConfiguration="NetTcpBinding_IStream" contract="Service.IStream"
            name="NetTcpBinding_IStream">

        </endpoint>
    </client>
</system.serviceModel>

Lancez le service puis le client. Il diffusera un gros fichier sans problème.

44
DerekGn