Vlastní kodér zpráv: Kompresní kodér

Ukázka komprese ukazuje, jak implementovat vlastní kodér pomocí platformy Windows Communication Foundation (WCF).

Ukázkové podrobnosti

Tato ukázka se skládá z programu klientské konzoly (.exe), programu konzoly místní služby (.exe) a knihovny kodéru komprese zpráv (.dll). Služba implementuje kontrakt, který definuje komunikační vzor žádosti a odpovědi. Kontrakt je definován rozhraním ISampleServer , které zveřejňuje základní operace ozvěny řetězců (Echo a BigEcho). Klient provádí synchronní požadavky na danou operaci a služba odpoví opakováním zprávy zpět klientovi. Aktivita klienta a služby je viditelná v oknech konzoly. Záměrem této ukázky je ukázat, jak napsat vlastní kodér a předvést dopad komprese zprávy na drát. Do kodéru komprese zpráv můžete přidat instrumentaci pro výpočet velikosti zprávy, doby zpracování nebo obojího.

Poznámka:

V rozhraní .NET Framework 4 byla pro klienta WCF povolena automatická dekomprimace, pokud server odesílá komprimovanou odpověď (vytvořenou pomocí algoritmu, jako je GZip nebo Deflate). Pokud je služba hostovaná na internetovém informačním serveru (IIS), může být služba IIS nakonfigurovaná tak, aby odesílala komprimovanou odpověď. Tuto ukázku můžete použít, pokud je požadavek na kompresi a dekompresi na straně klienta i služby nebo v případě, že je služba hostovaná samostatně.

Ukázka ukazuje, jak sestavit a integrovat vlastní kodér zpráv do aplikace WCF. Knihovna GZipEncoder.dll se nasadí s klientem i službou. Tato ukázka také ukazuje dopad komprimace zpráv. Kód v GZipEncoder.dll ukazuje následující:

  • Vytvoření vlastního kodéru a objektu pro vytváření kodérů

  • Vývoj elementu vazby pro vlastní kodér

  • Použití vlastní konfigurace vazby pro integraci vlastních prvků vazby.

  • Vývoj vlastní obslužné rutiny konfigurace umožňující konfiguraci souboru vlastního elementu vazby

Jak už jsme uvedli dříve, ve vlastním kodéru je implementovaných několik vrstev. Pro lepší znázornění vztahu mezi jednotlivými vrstvami je zjednodušené pořadí událostí pro spuštění služby v následujícím seznamu:

  1. Spustí se server.

  2. Informace o konfiguraci se čtou.

    1. Konfigurace služby zaregistruje vlastní obslužnou rutinu konfigurace.

    2. Hostitel služby se vytvoří a otevře.

    3. Element vlastní konfigurace vytvoří a vrátí vlastní element vazby.

    4. Element vlastní vazby vytvoří a vrátí továrnu kodéru pro kódování zpráv.

  3. Byla přijata zpráva.

  4. Objekt pro kódování zpráv vrátí kodér zpráv pro čtení zprávy a zapíše odpověď.

  5. Vrstva kodéru se implementuje jako objekt pro vytváření tříd. Pro vlastní kodér musí být veřejně zpřístupněn pouze objekt pro vytváření tříd kodéru kodéru. Objekt továrny je vrácen element vazby při vytvoření objektu ServiceHost nebo ChannelFactory<TChannel> objektu. Kodéry zpráv můžou fungovat v režimu ukládání do vyrovnávací paměti nebo streamování. Tato ukázka ukazuje režim vyrovnávací paměti i režim streamování.

Pro každý režim existuje doprovodná ReadMessage metoda a WriteMessage metoda abstraktní MessageEncoder třídy. V těchto metodách probíhá většina práce kódování. Ukázka zalomí stávající textové a binární kodéry zpráv. To umožňuje ukázce delegovat čtení a zápis přenosu zpráv do vnitřního kodéru a umožňuje komprimační kodér komprimovat nebo dekomprimovat výsledky. Vzhledem k tomu, že neexistuje žádný kanál pro kódování zpráv, je to jediný model pro použití více kodérů ve WCF. Jakmile je zpráva dekomprimovaná, výsledná zpráva se předá zásobníku pro zpracování zásobníku kanálu. Během komprese se výsledná komprimovaná zpráva zapíše přímo do zadaného datového proudu.

Tato ukázka používá pomocné metody (CompressBuffer a DecompressBuffer) k provedení převodu z vyrovnávacích pamětí na datové proudy pro použití GZipStream třídy.

Vyrovnávací paměť ReadMessage a WriteMessage třídy využívají BufferManager třídu. Kodér je přístupný pouze prostřednictvím továrny kodéru. Abstraktní MessageEncoderFactory třída poskytuje vlastnost pojmenovanou Encoder pro přístup k aktuálnímu kodéru a metodu pojmenovanou CreateSessionEncoder pro vytvoření kodéru, který podporuje relace. Takový kodér lze použít ve scénáři, ve kterém kanál podporuje relace, je seřazený a je spolehlivý. Tento scénář umožňuje optimalizaci v každé relaci dat zapsaných do drátu. Pokud to není žádoucí, základní metoda by neměla být přetížena. Vlastnost Encoder poskytuje mechanismus pro přístup k kodéru bez relace a výchozí implementace CreateSessionEncoder metody vrátí hodnotu vlastnosti. Vzhledem k tomu, že ukázka zabalí existující kodér pro zajištění komprese, MessageEncoderFactory implementace přijme MessageEncoderFactory , která představuje vnitřní kodér továrny.

Teď, když je definován kodér a objekt pro vytváření kodérů, je možné je použít s klientem a službou WCF. Tyto kodéry ale musí být přidány do zásobníku kanálů. Třídy můžete odvodit z tříd ServiceHost a ChannelFactory<TChannel> přepsat OnInitialize metody pro ruční přidání této kodéru factory. Objekt pro vytváření kodérů můžete zpřístupnit také prostřednictvím vlastního elementu vazby.

Chcete-li vytvořit nový vlastní binding element, odvození třídy z BindingElement třídy. Existuje však několik typů vazeb prvků. Chcete-li zajistit, aby vlastní binding element byl rozpoznán jako element vazby kódování zprávy, musíte také implementovat MessageEncodingBindingElement. Zveřejňuje MessageEncodingBindingElement metodu pro vytvoření nové továrny pro kodér zpráv (CreateMessageEncoderFactory), která se implementuje tak, aby vrátila instanci odpovídající továrny pro kodér zpráv. Kromě toho má vlastnost označující MessageEncodingBindingElement verzi adresování. Vzhledem k tomu, že tato ukázka zabalí existující kodéry, ukázková implementace také zabalí existující prvky vazby kodéru a vezme element vazby vnitřního kodéru jako parametr konstruktoru a zpřístupní ho prostřednictvím vlastnosti. Následující ukázkový kód ukazuje implementaci GZipMessageEncodingBindingElement třídy.

public sealed class GZipMessageEncodingBindingElement
                        : MessageEncodingBindingElement //BindingElement
                        , IPolicyExportExtension
{

    //We use an inner binding element to store information
    //required for the inner encoder.
    MessageEncodingBindingElement innerBindingElement;

        //By default, use the default text encoder as the inner encoder.
        public GZipMessageEncodingBindingElement()
            : this(new TextMessageEncodingBindingElement()) { }

    public GZipMessageEncodingBindingElement(MessageEncodingBindingElement messageEncoderBindingElement)
    {
        this.innerBindingElement = messageEncoderBindingElement;
    }

    public MessageEncodingBindingElement InnerMessageEncodingBindingElement
    {
        get { return innerBindingElement; }
        set { innerBindingElement = value; }
    }

    //Main entry point into the encoder binding element.
    // Called by WCF to get the factory that creates the
    //message encoder.
    public override MessageEncoderFactory CreateMessageEncoderFactory()
    {
        return new
GZipMessageEncoderFactory(innerBindingElement.CreateMessageEncoderFactory());
    }

    public override MessageVersion MessageVersion
    {
        get { return innerBindingElement.MessageVersion; }
        set { innerBindingElement.MessageVersion = value; }
    }

    public override BindingElement Clone()
    {
        return new
        GZipMessageEncodingBindingElement(this.innerBindingElement);
    }

    public override T GetProperty<T>(BindingContext context)
    {
        if (typeof(T) == typeof(XmlDictionaryReaderQuotas))
        {
            return innerBindingElement.GetProperty<T>(context);
        }
        else
        {
            return base.GetProperty<T>(context);
        }
    }

    public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.BuildInnerChannelFactory<TChannel>();
    }

    public override IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.BuildInnerChannelListener<TChannel>();
    }

    public override bool CanBuildChannelListener<TChannel>(BindingContext context)
    {
        if (context == null)
            throw new ArgumentNullException("context");

        context.BindingParameters.Add(this);
        return context.CanBuildInnerChannelListener<TChannel>();
    }

    void IPolicyExportExtension.ExportPolicy(MetadataExporter exporter, PolicyConversionContext policyContext)
    {
        if (policyContext == null)
        {
            throw new ArgumentNullException("policyContext");
        }
       XmlDocument document = new XmlDocument();
       policyContext.GetBindingAssertions().Add(document.CreateElement(
            GZipMessageEncodingPolicyConstants.GZipEncodingPrefix,
            GZipMessageEncodingPolicyConstants.GZipEncodingName,
            GZipMessageEncodingPolicyConstants.GZipEncodingNamespace));
    }
}

Všimněte si, že GZipMessageEncodingBindingElement třída implementuje rozhraní, takže tento element vazby IPolicyExportExtension lze exportovat jako zásadu v metadatech, jak je znázorněno v následujícím příkladu.

<wsp:Policy wsu:Id="BufferedHttpSampleServer_ISampleServer_policy">
    <wsp:ExactlyOne>
      <wsp:All>
        <gzip:text xmlns:gzip=
        "http://schemas.microsoft.com/ws/06/2004/mspolicy/netgzip1" />
       <wsaw:UsingAddressing />
     </wsp:All>
   </wsp:ExactlyOne>
</wsp:Policy>

Třída GZipMessageEncodingBindingElementImporter implementuje IPolicyImportExtension rozhraní, tato třída importuje zásady pro GZipMessageEncodingBindingElement. nástroj Svcutil.exe lze použít k importu zásad do konfiguračního souboru, pro zpracování GZipMessageEncodingBindingElement, do Svcutil.exe.config by se mělo přidat následující.

<configuration>
  <system.serviceModel>
    <extensions>
      <bindingElementExtensions>
        <add name="gzipMessageEncoding"
          type=
            "Microsoft.ServiceModel.Samples.GZipMessageEncodingElement, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
      </bindingElementExtensions>
    </extensions>
    <client>
      <metadata>
        <policyImporters>
          <remove type=
"System.ServiceModel.Channels.MessageEncodingBindingElementImporter, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
          <extension type=
"Microsoft.ServiceModel.Samples.GZipMessageEncodingBindingElementImporter, GZipEncoder, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>
</configuration>

Teď, když existuje odpovídající element vazby pro kodér komprese, může být programově připojen ke službě nebo klientovi vytvořením nového objektu vlastní vazby a přidáním vlastního elementu vazby do něj, jak je znázorněno v následujícím vzorovém kódu.

ICollection<BindingElement> bindingElements = new List<BindingElement>();
HttpTransportBindingElement httpBindingElement = new HttpTransportBindingElement();
GZipMessageEncodingBindingElement compBindingElement = new GZipMessageEncodingBindingElement ();
bindingElements.Add(compBindingElement);
bindingElements.Add(httpBindingElement);
CustomBinding binding = new CustomBinding(bindingElements);
binding.Name = "SampleBinding";
binding.Namespace = "http://tempuri.org/bindings";

I když to může stačit pro většinu uživatelských scénářů, podpora konfigurace souborů je důležitá, pokud je služba hostovaná na webu. Chcete-li podporovat scénář hostovaný webem, musíte vyvinout vlastní obslužnou rutinu konfigurace, aby byl vlastní element vazby možné konfigurovat v souboru.

Můžete vytvořit obslužnou rutinu konfigurace pro element vazby nad konfiguračním systémem. Obslužná rutina konfigurace pro element vazby musí být odvozena z BindingElementExtensionElement třídy. Informuje BindingElementExtensionElement.BindingElementType konfigurační systém o typu elementu vazby, který se má vytvořit pro tuto část. Všechny aspekty BindingElement , které lze nastavit, by měly být vystaveny jako vlastnosti v odvozené BindingElementExtensionElement třídě. Pomáhá ConfigurationPropertyAttribute při mapování atributů elementu konfigurace na vlastnosti a nastavení výchozích hodnot, pokud chybí atributy. Po načtení a použití hodnot z konfigurace vlastnosti je BindingElementExtensionElement.CreateBindingElement volána metoda, která převede vlastnosti na konkrétní instanci elementu vazby. Metoda BindingElementExtensionElement.ApplyConfiguration se používá k převodu vlastností odvozené BindingElementExtensionElement třídy na hodnoty, které mají být nastaveny na nově vytvořený binding element.

Následující vzorový kód ukazuje implementaci GZipMessageEncodingElement.

public class GZipMessageEncodingElement : BindingElementExtensionElement
{
    public GZipMessageEncodingElement()
    {
    }

//Called by the WCF to discover the type of binding element this
//config section enables
    public override Type BindingElementType
    {
        get { return typeof(GZipMessageEncodingBindingElement); }
    }

    //The only property we need to configure for our binding element is
    //the type of inner encoder to use. Here, we support text and
    //binary.
    [ConfigurationProperty("innerMessageEncoding",
                         DefaultValue = "textMessageEncoding")]
    public string InnerMessageEncoding
    {
        get { return (string)base["innerMessageEncoding"]; }
        set { base["innerMessageEncoding"] = value; }
    }

    //Called by the WCF to apply the configuration settings (the
    //property above) to the binding element
    public override void ApplyConfiguration(BindingElement bindingElement)
    {
        GZipMessageEncodingBindingElement binding =
                (GZipMessageEncodingBindingElement)bindingElement;
        PropertyInformationCollection propertyInfo =
                    this.ElementInformation.Properties;
        if (propertyInfo["innerMessageEncoding"].ValueOrigin !=
                                     PropertyValueOrigin.Default)
        {
            switch (this.InnerMessageEncoding)
            {
                case "textMessageEncoding":
                    binding.InnerMessageEncodingBindingElement =
                      new TextMessageEncodingBindingElement();
                    break;
                case "binaryMessageEncoding":
                    binding.InnerMessageEncodingBindingElement =
                         new BinaryMessageEncodingBindingElement();
                    break;
            }
        }
    }

    //Called by the WCF to create the binding element
    protected override BindingElement CreateBindingElement()
    {
        GZipMessageEncodingBindingElement bindingElement =
                new GZipMessageEncodingBindingElement();
        this.ApplyConfiguration(bindingElement);
        return bindingElement;
    }
}

Tato obslužná rutina konfigurace se mapuje na následující reprezentaci v souboru App.config nebo Web.config pro službu nebo klienta.

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

Chcete-li použít tuto obslužnou rutinu konfigurace, musí být registrována v elementu <system.serviceModel> , jak je znázorněno v následující ukázkové konfiguraci.

<extensions>
    <bindingElementExtensions>
       <add
           name="gzipMessageEncoding"
           type=
           "Microsoft.ServiceModel.Samples.GZipMessageEncodingElement,
           GZipEncoder, Version=1.0.0.0, Culture=neutral,
           PublicKeyToken=null" />
      </bindingElementExtensions>
</extensions>

Při spuštění serveru se v okně konzoly zobrazí požadavky na operace a odpovědi. Server vypnete stisknutím klávesy ENTER v okně.

Press Enter key to Exit.

        Server Echo(string input) called:
        Client message: Simple hello

        Server BigEcho(string[] input) called:
        64 client messages

Při spuštění klienta se v okně konzoly zobrazí požadavky na operace a odpovědi. Stisknutím klávesy ENTER v okně klienta klienta ukončete klienta.

Calling Echo(string):
Server responds: Simple hello Simple hello

Calling BigEcho(string[]):
Server responds: Hello 0

Press <ENTER> to terminate client.

Nastavení, sestavení a spuštění ukázky

  1. Pomocí následujícího příkazu nainstalujte ASP.NET 4.0:

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Ujistěte se, že jste pro ukázky windows Communication Foundation provedli jednorázovou instalační proceduru.

  3. Pokud chcete sestavit řešení, postupujte podle pokynů v části Sestavení ukázek Windows Communication Foundation.

  4. Pokud chcete spustit ukázku v konfiguraci s jedním nebo více počítači, postupujte podle pokynů v části Spuštění ukázek windows Communication Foundation.