Compressão em Serviços WCF

by Israel Aece 18. March 2010 00:16

Em certas situações, quando um cliente executa uma operação de um serviço, o resultado pode ser uma grande massa de dados. Essa massa de dados pode ser qualquer coisa, desde os bytes de um arquivo até mesmo objetos que foram extraídos de um banco de dados.

Como todos sabem, o problema disso é a quantidade de informações que irá trafegar na rede, podendo causar uma grande lentidão, caso isso seja uma operação comum. A principal solução que geralmente utilizamos quando queremos diminuir o tráfego, é a compactação das informações. Isso fará com que a quantidade total de dados seja bem menor, aliviando consideravelmente a quantidade de informações que são trafegadas. Obviamente que a compactação tem um overhead quando você compacta ou descompacta, e isso deve ser levado em consideração para decidir se deve ou não optar por ela, mas na maioria das vezes, acaba compensando.

Mas infelizmente o WCF não traz a possibilidade de compactar e/ou descompactar as mensagens que são enviadas. Sendo assim, para conseguirmos efetuar a compactação, temos que recorrer aos pontos de estensibilidade de WCF, para conseguir interceptar o envio e recebimento das mensagens, para assim conseguir diminuir o conteúdo a ser trafegado. A Microsoft se preocupou em deixar a disposição de todos, um exemplo que estende o WCF, e utiliza a classe GZipStream (System.IO.Compression) para compactar as mensagens; além deste exemplo, há um projeto chamado WCF Extensions, que possui essa funcionalidade de compactação. É importante dizer que em ambos os lados (cliente e serviço) precisam acoplar o código para efetuar a compactação e descompactação, caso contrário, a mensagem não poderá ser lida.

Além disso, para aqueles que hospedam o serviço no IIS, podem tirar proveito da compactação que ele faz. O IIS fornece um serviço que permite efetuar a compactação de conteúdo dinâmico, que é o caso de serviços WCF. Quando habilitado, este serviço irá compactar a resposta que será enviada ao cliente. O IIS compacta a resposta dependendo de um header que vem na requisição, indicando se o cliente consegue ou não interpretar o conteúdo compactado. Como os bindings do WCF não fornecem isso, você tem que explicitamente adicionar um header para encaminhar ao servidor (IIS) que você consegue compreender o algoritmo GZIP ou o Deflate. Esse header é o Accept-Encoding, e para fazer isso podemos recorrer ao código abaixo:

using (ServiceClient p = new ServiceClient())
{
    using (new OperationContextScope(p.InnerChannel))
    {
        var props = new HttpRequestMessageProperty();
        props.Headers.Add(HttpRequestHeader.AcceptEncoding, "gzip,deflate");
        OperationContext.Current.OutgoingMessageProperties[HttpRequestMessageProperty.Name] = props;

        Console.WriteLine(p.RetornaUmTextoMuitoGrande());
    }
}

Ao monitorar a requisição através do Fiddler, você poderá perceber que essas informações são encaminhadas ao IIS, incluindo o header necessário:

POST /MeuServico/Service.svc HTTP/1.1
Content-Type: text/xml; charset=utf-8
Accept-Encoding: gzip,deflate
SOAPAction: "http://tempuri.org/IService/RetornaUmTextoMuitoGrande"
Host: israelaecenb1
Content-Length: 151
Expect: 100-continue
Connection: Keep-Alive

Com isso, a mensagem de retorno que antes tinha um total de 2.000.176 bytes, passa a ter apenas 17.494 bytes usando o GZIP. Mas apesar de conseguirmos visualizar o conteúdo compactado, isso não quer dizer o proxy, ou melhor, o binding que está do lado do cliente, irá conseguir interpretar o mesmo. Como comentado acima, pelo fato do WCF não suportar nativamente a compactação, uma exceção do tipo ProtocolException será disparada, indicando exatamente isso.

Na versão 4.0 do WCF, uma nova propriedade foi adicionada na classe HttpTransportElement, chamada de DecompressionEnabled. Essa propriedade recebe um valor boleano indicando se o WCF pode ou não compactar as mensagens de resposta que são enviadas ao cliente. Por padrão, essa propriedade é definida como True, mas para conseguir acessá-la, é necessário a criação de um binding customizado. Um outro ponto importante é com relação à mensagem de requisição, que ao contrário das versões anteriores, a versão 4.0 do WCF já embuti o header necessário (Accept-Encoding) para que o serviço saiba que o cliente consegue descompactar a resposta.

Tags: , , , ,

WCF

Compartilhamento de Bindings

by Israel Aece 30. November 2009 08:25

Quando você possui múltiplos endpoints para expor um serviço, na maioria das vezes, esses endpoints possuem características diferentes, pois você poderá publicá-lo através de HTTP para consumo externo, enquanto as aplicações locais, consomem este mesmo serviço através de TCP, para uma melhor performance.

A configuração abaixo ilustra a exposição de um mesmo serviço e de um mesmo contrato através dos protocolos TCP e HTTP. Cada um dos protocolos possuem características diferentes de comunicação, e justamente por isso, exigem bindings diferentes.

<system.serviceModel>
  <services>
    <service name="App.Servico">
      <host>
        <baseAddresses>
          <add baseAddress="net.tcp://localhost:9838"/>
          <add baseAddress="http://localhost:8478"/>
        </baseAddresses>
      </host>
      <endpoint address="srv"
                contract="App.IContrato1"
                binding="netTcpBinding"/>
      <endpoint address="srv"
                contract="App.IContrato1"
                binding="basicHttpBinding"/>
    </service>
  </services>
</system.serviceModel>

Cada endpoint é composto por um endereço, binding e contrato, e cada endpoint possui seu próprio listener e channel stack, que varia de acordo com o binding escolhido. Em algumas situações, é comum ter mais do que um contrato para o mesmo serviço, onde queremos compartilhar o mesmo endereço para ambos contratos. Abaixo podemos ver um serviço que tem esse comportamento:

<system.serviceModel>
  <services>
    <service name="App.Servico">
      <host>
        <baseAddresses>
          <add baseAddress="net.tcp://localhost:9838"/>
        </baseAddresses>
      </host>
      <endpoint address="srv"
                contract="App.IContrato1"
                binding="netTcpBinding"/>
      <endpoint address="srv"
                contract="App.IContrato2"
                binding="netTcpBinding"/>
    </service>
  </services>
</system.serviceModel>

Repare que temos dois contratos (IContrato1 e IContrato2) expostos através de TCP e usando o mesmo endereço. Isso somente é possível porque o WCF reutiliza a mesma instância do binding para efetuar a comunicação, e difere a execução baseando-se nas Soap Actions. Se cada binding tiver sua configuração específica (definido através do atributo bindingConfiguration), então isso fará com que o WCF crie instâncias diferentes do mesmo binding, resultando em uma exceção tipo do InvalidOperationException sendo disparada, informando que não é permitido associar uma mesma instância binding à endereços diferentes.

Se você tiver endereços distintos, então duas instâncias distintas do binding correspondente serão criadas para atender cada um deles. Esse mesmo cuidado você precisa ter ao configurar o serviço de forma imperativa. Ao chamar o método AddServiceEndpoint da classe ServiceHost, você deverá se preocupar em passar a mesma instância do binding, como por exemplo:

using (ServiceHost host = 
       new ServiceHost(typeof(Servico), new Uri[] { new Uri("net.tcp://localhost:9838") }))
{
    NetTcpBinding b = new NetTcpBinding();

    host.AddServiceEndpoint(typeof(IContrato1), b, "srv");
    host.AddServiceEndpoint(typeof(IContrato2), b, "srv");

    host.Open();
    Console.ReadLine();
}

Tags: , , , ,

WCF

Serviços TCP no Silverlight

by Israel Aece 20. November 2009 21:07

A cada dia que o passa, o Silverlight tem ganhado cada vez mais espaço nos sites que são construídos ao redor do mundo. Motivo para a Microsoft evoluir essa tecnologia rapidamente, adicionando cada vez mais funcionalidades dentro dele.

Os adeptos ao Silverlight não se limitam à aqueles construtores de sites institucionais, que na maioria das vezes, são criados para campanhas publicitárias e com poucas funcionalidades dinâmicas. Ele também está atingindo as empresas desenvolvem os softwares para uso interno, justamente porque a Microsoft incorpora nele, funcionalidades que permitem o uso em aplicações do tipo LOB (Line of Business).

Uma das novidades mais recentes, que afeta principalmente o uso de aplicações Silverlight em intranets, é que a partir da versão 4.0, teremos a possibilidade de referenciar e consumir serviços construídos em WCF, através do protocolo TCP (net.tcp).

Como disse no parágrafo acima, esse tipo de protocolo é útil em intranets, já que não haverá restrições de eventuais firewalls, e mesmo que tenha, tudo é negociável. É importante dizer, que as aplicações Silverlight estão condicionadas à utilizarem o seguinte intervalo de portas: 4502 à 4534, ou seja, somente conseguirá acessar um serviço WCF exposto através de uma delas.

Uma das principais características do protocolo TCP, é que possibilita a comunicação duplex, ou seja, permite que o serviço se comunique com o cliente, através de callbacks. Isso já era possível no Silverlight, através do Polling Duplex, que nada mais é do que uma especialização do protocolo BasicHttpBinding, mas que simula callbacks em cima do protocolo HTTP.

Outra grande vantagem do protocolo TCP quando comparado com o HTTP, é a performance, e mesmo em aplicações Silverlight, essa vantagem é bastante significativa, e um dos grande responsáveis por isso, é a codificação binária. Neste momento, para usar este protocolo, você deve abrir mão da segurança em nível de transporte, que não é suportado.

Os tipos necessários para acessar um serviço WCF através de TCP, estão contidos no assembly System.ServiceModel.NetTcp.dll, localizado em %Program Files%\Microsoft SDKs\Silverlight\v4.0\Libraries\Client. Mas isso não quer dizer que você terá uma classe chamada NetTcpBinding, assim como há no .NET Full. O Silverlight utiliza um CustomBinding, composto com os seguintes bindings elements: BinaryMessageEncodingBindingElement e TcpTransportBindingElement. Ao referenciar o serviço TCP em uma aplicação Silverlight 4.0, a IDE do Visual Studio 2010 já faz as seguintes entradas no respectivo arquivo de configuração:

<configuration>
    <system.serviceModel>
        <bindings>
            <customBinding>
                <binding name="NetTcpBinding_IContrato">
                    <binaryMessageEncoding />
                    <tcpTransport
                        maxReceivedMessageSize="2147483647"
                        maxBufferSize="2147483647" />
                </binding>
            </customBinding>
        </bindings>
        <client>
            <endpoint
                address="net.tcp://localhost:4502/srv"
                binding="customBinding"
                bindingConfiguration="NetTcpBinding_IContrato"
                contract="Servico.IContrato"
                name="NetTcpBinding_IContrato" />
        </client>
    </system.serviceModel>
</configuration>

Ao contrário de outros tipos de comunicação e de aplicações, não basta simplesmente termos o cliente e o serviço funcionando para que eles possam se comunicar. Mesmo através de TCP, este protocolo também está condicionado às políticas de cross-domain.

Para comunicação HTTP, tudo o que precisamos é definir um arquivo XML na aplicação, que permite o acesso. Para suportar a comunicação através de TCP, o Silverlight resolve a questão da restrição de cross-domain de uma forma mais rebuscada. A Microsoft criou uma template de projeto chamada Silverlight TCP Socket Policy, que pode ser acessada a partir das templates Online do Visual Studio 2010 ou através do Visual Studio Gallery, neste endereço.

Essa template dá origem à um projeto do tipo Console, que expõe o arquivo de cross-domain (definido no arquivo SocketPolicy.cs), definindo o intervalo de portas que é permitido que aplicações Silverlight utilizem. Ao rodar as aplicações, certifique-se também de que este projeto esteja rodando, caso contrário, você receberá uma exceção do tipo CommunicationException, informando que você não tem permissões para efetuar o acesso ao serviço, sugerindo que talvez esteja faltando o arquivo de cross-domain.

Conclusão: Apesar de ainda estar em sua versão Beta, o Silverlight 4.0 traz uma série de melhorias e novas funcionalidades, que facilitará cada vez mais a criação de aplicações LOB, e com certeza, uma das principais limitações até então, era o consumo de serviços WCF através do protocolo TCP, que é fator determinante em aplicações deste tipo.

Tags: , , ,

WCF

SessionId diferente com TCP

by Israel Aece 11. September 2009 08:01

Como já disse em diversos lugares, o WCF fornece o modelo de gerenciamento de instância chamado de PerSession. Neste modelo, haverá uma instância da classe que representa o serviço para cada instância do proxy do lado do cliente. Com isso, utilizamos a propriedade SessionId, que retornará um GUID representando a identificação da instância, para que o runtime do WCF consiga correlacionar as mensagens. O código abaixo ilustra como podemos proceder para visualizar esse “Id”:

//Serviço
Debug.WriteLine(OperationContext.Current.SessionId);

//Cliente
Debug.WriteLine(proxy.InnerChannel.SessionId);

Em princípio, ao executar esses códigos, eles teriam que retornar a mesma informação. Mas há situações em que isso não acontece. Quando definimos o modo de gerenciamento de instância do serviço como PerSession e habilitamos a funcionalidade de garantia de entrega (Reliable Messages), essa combinação é referida como Reliable Sessions.

Ao utilizar o protocolo TCP em conjunto com este recurso, o cliente somente poderá capturar o “Id” após a chamada para o primeiro método ou após abrir explicitamente o proxy, através do método Open, ações que efetivamente criarão a sessão. Do contrário, a propriedade SessionId do lado do cliente sempre retornará nulo. Agora, se utilizar TCP com o recurso de sessões confiáveis desabilitado (que é o padrão), o cliente poderá acessar a propriedade SessionId antes de fazer qualquer chamada para o serviço, mas obterá um “Id” diferente daquela do serviço. Apesar de diferentes, o WCF consegue internamente lidar com isso, e correlacionar as mensagens, mas você não pode garantir a igualdade dos “Ids” em ambos os lados.

Se estiver com esse problema, o que precisa fazer é habilitar isso tanto no serviço quanto no cliente. Se utilizar o modo imperativo, pode utilizar uma versão (overload) do construtor do binding NetTcpBinding, que recebe um valor boleano indicando se a Reliable Session está ou não habilitada. Já no modo declarativo, pode utilizar o atributo enabled do elemento reliableSession, definindo-o como True. Abaixo estão os exemplos:

[ Modo Imperativo ]
NetTcpBinding tcp = new NetTcpBinding(SecurityMode.None, true);

[ Modo Declarativo ]
<reliableSession enabled="true" ordered="false" inactivityTimeout="00:10:00" />

Tags: ,

WCF

WCF Vídeo - Hospedando um Serviço

by Israel Aece 18. August 2009 08:08
Hospedando um Serviço Hospedando um Serviço

Criar o contrato e a classe que representa o serviço não é o suficiente para que ele funcione. O hosting é o responsável por gerenciar a execução do serviço, que determinará como, onde e o que será disponibilizado aos consumidores. Este vídeo irá mostrar as possibilidades de hosting que temos no WCF. Para maiores detalhes, consulte este artigo.

Formato: WMV - Duração: 00:41:44 - Tamanho: 52MB

Tags: , , , ,

WCF

Cuidados ao utilizar o WSDualHttpBinding

by Israel Aece 8. July 2009 10:59

Há algum tempo eu escrevi um artigo que fala sobre a possibilidade que temos para efetuar callbacks do serviço para o cliente que, em outras palavras, significa o serviço se comunicando com o cliente, permitindo ao mesmo notificar sobre o término de alguma tarefa, simular eventos, etc. Esse tipo de comunicação é também referida/conhecida com "duplex".

Quando queremos esta funcionalidade e o serviço está exposto via TCP, através do binding NetTcpBinding, o WCF utiliza o mesmo canal para efetuar a comunicação entre o serviço e o cliente. Já quando o serviço for exposto através do protocolo HTTP, utilizamos o binding WSDualHttpBinding, responsável por possibilitar callbacks através do HTTP. Como o HTTP é um protocolo unidirecional, esse binding é responsável por também criar um endpoint do lado do cliente (diferente do usado para envio das mensagens), que receberá os callbacks que o serviço enviará.

Enquanto você trabalha no desenvolvimento do serviço/cliente, que na maioria das vezes é na máquina local, tudo funciona perfeitamente. Alguns problemas começam a aparecer quando você faz a distribuição dos aplicativos que consomem o serviço via HTTP. O primeiro deles é quando você instala o serviço em uma máquina com Windows XP e que tenha o IIS instalado (que não tem o HTTP.sys). A ausência do HTTP.sys não permite ao Windows compartilhar uma mesma porta entre múltiplas aplicações. Por padrão, o WSDualHttpBinding irá criar e configurar o endpoint com a porta 80, ou seja, a mesma utilizada pelo IIS, resultando em uma exceção do tipo AddressAlreadyInUseException. Você pode facilmente resolver isso removendo ou parando o serviço do IIS ou especificando uma outra porta. Para utilizar uma porta diferente, basta utilizar o atributo clientBaseAddress (elemento binding) na configuração do binding do lado do cliente.

Já o segundo problema está relacionado a solução do primeiro. Se você altera a porta padrão, muito provavelmente ela estará barrada no firewall do Windows. Isso quer dizer que se o firewall estiver ativo (que é o recomendado), o serviço não conseguirá enviar o callback para o cliente. Para resolver isso, você pode adicionar uma exceção no firewall do Windows. Você pode fazer isso diretamente através do gerenciador do firewall no Painel de Controle, ou até mesmo via programação.

Tags: , , ,

WCF

Configurando o limite de conexões pendentes

by Israel Aece 24. March 2009 11:59

Quando expomos um serviço através do binding NetTcpBinding, as requisições que chegam até ele, são processadas e, finalmente o retorno é enviado para o respectivo cliente. As mensagens que chegam são processadas no formato FIFO (first-in, first out), ou seja, elas aguardam o processamento em uma fila e, sob demanda, são encaminhadas para o processamento.

Se a velocidade do processamento da mensagem é mais lenta que a velocidade de envio das mensagens, essa fila pode aumentar. Por padrão o binding NetTcpBinding limita, através da propriedade ListenBacklog, o número de requisições pendentes que podem ser enfileiradas. O valor padrão é 10 e, dependendo da quantidade de requisições que chegam para o serviço, esse limite pode exceder e, a seguinte exceção será disparada:

System.ServiceModel.EndpointNotFoundException: Could not connect to net.tcp://localhost:8879/srv. The connection attempt lasted for a time span of 00:00:02.1404880. TCP error code 10061: No connection could be made because the target machine actively refused it 127.0.0.1:8879.  ---> System.Net.Sockets.SocketException:No connection could be made because the target machine actively refused it 127.0.0.1:8879

A alternativa aqui é aumentar esse número, definindo um valor próximo a quantidade das conexões simultaneas que eventualmente podem chegar para o serviço. O exemplo abaixo ilustra a configuração desta propriedade no binding NetTcpBinding:

host.AddServiceEndpoint(typeof(IContrato), new NetTcpBinding() { ListenBacklog = 70 }, "srv");

Tags: ,

CSD | WCF

Suporte ao protocolo UDP

by Israel Aece 18. January 2009 16:10

Continuando minhas deambulações com o Visual Studio .NET 2010, notei que a Microsoft incorporou ao .NET Framework 4.0 novos tipos para suportar o protocolo UDP no WCF. Similiar ao TCP, pelo fato de enviar e receber pacotes na rede, possui algumas diferenças consideráveis, como o fato de não manter conexão ativa entre as duas extremidades, sessões e garantia de entrega (não existe o conceito de acks no UDP).

Justamente por não ter todo o handshake e o overhead que existe para suportar as funcionalidades que falamos acima, o protocolo UDP possui uma performance bem melhor em relação ao TCP. Na versão atual, não há um binding exclusivo para este protocolo e, para fazer o uso dele, é necessário recorrer a um binding customizado, configurando-o com a codificação binary e utilizando o meio de transporte UDP, sempre através de elementos de binding. Além disso, ainda há um novo schema: soap.udp. O código abaixo ilustra a sua utilização:

Uri address = new Uri("soap.udp://localhost:8383");
CustomBinding cb =
    new CustomBinding(
        new BindingElement[] { 
            new BinaryMessageEncodingBindingElement(), 
            new UdpTransportBindingElement()});

using (ServiceHost host = new ServiceHost(typeof(Servico), new Uri[] { address }))
{
    host.AddServiceEndpoint(typeof(IContrato), cb, "srv");
    host.Open();

    ((UdpTransportBindingElement)cb.Elements[0]).ManualAddressing = false;

    using (ChannelFactory<IContrato> f = 
        new ChannelFactory<IContrato>(cb, new EndpointAddress(address)))
    {
        f.CreateChannel().Ping();
    }
}

Tags: ,

CSD | WCF

Powered by BlogEngine.NET 1.5.0.0
Theme by Mads Kristensen

Sobre

Meu nome é Israel Aece e sou especialista em tecnologias de desenvolvimento Microsoft, atuando como desenvolvedor de aplicações para o mercado financeiro utilizando a plataforma .NET. [ Mais ]

Twitter

Host