다음을 통해 공유


사용자 지정 메시지 인코더: 압축 인코더

압축 샘플은 WCF(Windows Communication Foundation) 플랫폼을 사용하여 사용자 지정 인코더를 구현하는 방법을 보여 줍니다.

샘플 세부 정보

이 샘플은 클라이언트 콘솔 프로그램(.exe), 자체 호스팅 서비스 콘솔 프로그램(.exe) 및 압축 메시지 인코더 라이브러리(.dll)로 구성됩니다. 서비스는 요청-회신 통신 패턴을 정의하는 계약을 구현합니다. 계약은 기본 문자열 에코 작업을 노출하는 인터페이스 ISampleServer에 의해 정의됩니다 (EchoBigEcho). 클라이언트는 지정된 작업에 동기 요청을 하고 서비스는 메시지를 클라이언트에 다시 반복하여 회신합니다. 클라이언트 및 서비스 활동이 콘솔 창에 표시됩니다. 이 샘플의 목적은 사용자 지정 인코더를 작성하고 메시지 압축이 유선에 미치는 영향을 보여 주는 방법을 보여 주는 것입니다. 압축 메시지 인코더에 계측을 추가하여 메시지 크기, 처리 시간 또는 둘 다를 계산할 수 있습니다.

비고

.NET Framework 4에서 서버가 압축된 응답(GZip 또는 Deflate와 같은 알고리즘으로 생성됨)을 보내는 경우 WCF 클라이언트에서 자동 압축 해제가 사용하도록 설정되었습니다. 서비스가 IIS(인터넷 정보 서버)에서 웹 호스팅되는 경우 서비스에서 압축된 응답을 보내도록 IIS를 구성할 수 있습니다. 이 샘플은 클라이언트와 서비스 모두에서 압축 및 압축 해제를 수행해야 하거나 서비스가 자체 호스팅되는 경우 사용할 수 있습니다.

이 샘플에서는 사용자 지정 메시지 인코더를 빌드하고 WCF 애플리케이션에 통합하는 방법을 보여 줍니다. 라이브러리 GZipEncoder.dll 클라이언트와 서비스를 모두 사용하여 배포됩니다. 이 샘플에서는 메시지 압축의 영향도 보여 줍니다. GZipEncoder.dll 코드는 다음을 보여 줍니다.

  • 사용자 지정 인코더 및 인코더 팩터리 빌드

  • 사용자 지정 인코더에 대한 바인딩 요소 개발

  • 사용자 지정 바인딩 요소를 통합하기 위한 사용자 지정 바인딩 구성 사용

  • 사용자 지정 바인딩 요소의 파일 구성을 허용하는 사용자 지정 구성 처리기 개발

앞에서 설명한 것처럼 사용자 지정 인코더에 구현되는 여러 계층이 있습니다. 이러한 각 계층 간의 관계를 더 잘 설명하기 위해 서비스 시작에 대한 이벤트의 간소화된 순서는 다음 목록에 있습니다.

  1. 서버가 시작됩니다.

  2. 구성 정보를 읽습니다.

    1. 서비스 구성은 사용자 지정 구성 처리기를 등록합니다.

    2. 서비스 호스트가 만들어지고 열립니다.

    3. 사용자 지정 구성 요소는 사용자 지정 바인딩 요소를 만들고 반환합니다.

    4. 사용자 지정 바인딩 요소는 메시지 인코더 팩터리를 만들고 반환합니다.

  3. 메시지가 수신됩니다.

  4. 메시지 인코더 팩터리는 메시지에서 읽고 응답을 작성하기 위한 메시지 인코더를 반환합니다.

  5. 인코더 계층은 클래스 팩터리로 구현됩니다. 인코더 클래스 팩터리만 사용자 지정 인코더에 대해 공개적으로 노출되어야 합니다. ServiceHost 또는 ChannelFactory<TChannel> 개체를 생성할 때 바인딩 요소에 의해 팩터리 개체가 반환됩니다. 메시지 인코더는 버퍼링 또는 스트리밍 모드에서 작동할 수 있습니다. 이 샘플에서는 버퍼링 모드와 스트리밍 모드를 모두 보여 줍니다.

각 모드에는 추상 ReadMessage 클래스에 함께 제공되는 WriteMessage 메서드와 MessageEncoder 메서드가 있습니다. 대부분의 인코딩 작업은 이러한 메서드에서 수행됩니다. 샘플은 기존 텍스트 및 이진 메시지 인코더를 래핑합니다. 이렇게 하면 샘플에서 메시지의 와이어 표현을 읽고 쓰는 기능을 내부 인코더에 위임할 수 있으며 압축 인코더가 결과를 압축하거나 압축 해제할 수 있습니다. 메시지 인코딩에 대한 파이프라인이 없으므로 WCF에서 여러 인코더를 사용하는 유일한 모델입니다. 메시지가 압축 해제된 후, 결과 메시지가 채널 스택이 처리할 수 있도록 스택 상위로 전달됩니다. 압축하는 동안 결과 압축 메시지는 제공된 스트림에 직접 기록됩니다.

이 샘플에서는 도우미 메서드(CompressBufferDecompressBuffer)를 사용하여 버퍼에서 스트림으로 변환하여 클래스를 GZipStream 사용합니다.

버퍼링된 ReadMessage 클래스와 WriteMessage 클래스는 클래스를 BufferManager 사용합니다. 인코더는 인코더 팩터리를 통해서만 액세스할 수 있습니다. 추상 MessageEncoderFactory 클래스는 현재 인코더에 액세스하기 위해 명명된 Encoder 속성과 세션을 지원하는 인코더를 만들기 위해 명명된 CreateSessionEncoder 메서드를 제공합니다. 이러한 인코더는 채널이 세션을 지원하고 순서가 지정되고 신뢰할 수 있는 시나리오에서 사용할 수 있습니다. 이 시나리오를 사용하면 와이어에 기록된 데이터의 각 세션에서 최적화할 수 있습니다. 원하는 것이 아니면 기본 메서드가 오버로드되지 않아야 합니다. 이 속성은 Encoder 세션 없는 인코더에 액세스하기 위한 메커니즘을 제공하고 메서드의 CreateSessionEncoder 기본 구현은 속성 값을 반환합니다. 샘플은 압축을 제공하기 위해 기존 엔코더를 래핑하므로, 구현은 내부 인코더 팩터리를 나타내는 MessageEncoderFactory을 허용합니다.

이제 인코더 및 인코더 팩터리를 정의했으므로 WCF 클라이언트 및 서비스와 함께 사용할 수 있습니다. 그러나 이러한 인코더는 채널 스택에 추가해야 합니다. ServiceHostChannelFactory<TChannel> 클래스에서 클래스를 파생하고 OnInitialize 메서드를 재정의하여 이 인코더 팩터리를 수동으로 추가할 수 있습니다. 사용자 지정 바인딩 요소를 통해 인코더 팩터리를 노출할 수도 있습니다.

새 사용자 지정 바인딩 요소를 만들려면 BindingElement 클래스로부터 클래스를 파생합니다. 그러나 여러 유형의 바인딩 요소가 있습니다. 사용자 지정 바인딩 요소가 메시지 인코딩 바인딩 요소로 인식되도록 하려면 MessageEncodingBindingElement를 구현해야 합니다. 일치하는 MessageEncodingBindingElement 메시지 인코더 팩터리의 인스턴스를 반환하기 위해 구현되는 새 메시지 인코더 팩터리(CreateMessageEncoderFactory)를 만드는 메서드를 노출합니다. MessageEncodingBindingElement 또한 주소 지정 버전을 나타내는 속성이 있습니다. 이 샘플은 기존 인코더를 래핑하기 때문에 샘플 구현은 기존 인코더 바인딩 요소도 래핑하고 내부 인코더 바인딩 요소를 생성자에 대한 매개 변수로 사용하여 속성을 통해 노출합니다. 다음 샘플 코드는 GZipMessageEncodingBindingElement 클래스의 구현을 보여줍니다.

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));
    }
}

GZipMessageEncodingBindingElement 클래스가 IPolicyExportExtension 인터페이스를 구현하여 이 바인딩 요소를 메타데이터의 정책으로 내보낼 수 있음을 다음 예제에서 확인하십시오.

<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>

GZipMessageEncodingBindingElementImporter 클래스는 IPolicyImportExtension 인터페이스를 구현하고, 이 클래스는 GZipMessageEncodingBindingElement에 대한 정책을 가져옵니다. Svcutil.exe 도구를 사용하여 정책을 구성 파일로 가져오는 것 외에, GZipMessageEncodingBindingElement를 처리하기 위해 다음을 Svcutil.exe.config에 추가해야 합니다.

<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>

압축 인코더에 일치하는 바인딩 요소가 있으므로 다음 샘플 코드와 같이 새 사용자 지정 바인딩 개체를 생성하고 사용자 지정 바인딩 요소를 추가하여 서비스 또는 클라이언트에 프로그래밍 방식으로 연결할 수 있습니다.

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";

이는 대부분의 사용자 시나리오에 충분할 수 있지만 서비스가 웹 호스팅되는 경우 파일 구성을 지원하는 것이 중요합니다. 웹 호스팅 시나리오를 지원하려면 사용자 지정 바인딩 요소를 파일에서 구성할 수 있도록 사용자 지정 구성 처리기를 개발해야 합니다.

구성 시스템 위에 바인딩 요소에 대한 구성 처리기를 빌드할 수 있습니다. 바인딩 요소에 대한 구성 처리기는 클래스에서 BindingElementExtensionElement 파생되어야 합니다. 이 BindingElementExtensionElement.BindingElementType 섹션에 대해 만들 바인딩 요소의 형식을 구성 시스템에 알릴 수 있습니다. 설정할 수 있는 모든 측면은 BindingElement 파생 클래스의 BindingElementExtensionElement 속성으로 노출되어야 합니다. 구성 ConfigurationPropertyAttribute 요소 특성을 속성에 매핑하고 특성이 누락된 경우 기본값을 설정하는 데 도움이 됩니다. 구성의 값이 로드되고 속성 BindingElementExtensionElement.CreateBindingElement 에 적용되면 메서드가 호출되어 속성을 바인딩 요소의 구체적인 인스턴스로 변환합니다. 이 BindingElementExtensionElement.ApplyConfiguration 메서드는 파생 클래스의 BindingElementExtensionElement 속성을 새로 만든 바인딩 요소에 설정할 값으로 변환하는 데 사용됩니다.

다음 샘플 코드는 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;
    }
}

이 구성 처리기는 서비스 또는 클라이언트에 대한 App.config 또는 Web.config 다음 표현에 매핑됩니다.

<gzipMessageEncoding innerMessageEncoding="textMessageEncoding" />

이 구성 처리기를 사용하려면 다음 샘플 구성과 같이 system.serviceModel< 요소 내에> 등록해야 합니다.

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

서버를 실행하면 작업 요청 및 응답이 콘솔 창에 표시됩니다. 창에서 Enter 키를 눌러 서버를 종료합니다.

Press Enter key to Exit.

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

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

클라이언트를 실행하면 작업 요청 및 응답이 콘솔 창에 표시됩니다. 클라이언트 창에서 Enter 키를 눌러 클라이언트를 종료합니다.

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

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

Press <ENTER> to terminate client.

샘플을 설정, 빌드 및 실행하려면

  1. 다음 명령을 사용하여 ASP.NET 4.0을 설치합니다.

    %windir%\Microsoft.NET\Framework\v4.0.XXXXX\aspnet_regiis.exe /i /enable
    
  2. Windows Communication Foundation 샘플 에 대한One-Time 설정 절차를 수행했는지 확인합니다.

  3. 솔루션을 빌드하려면 Windows Communication Foundation 샘플 빌드의 지침을 따릅니다.

  4. 단일 또는 컴퓨터 간 구성에서 샘플을 실행하려면 Windows Communication Foundation 샘플실행의 지침을 따릅니다.