Transporte: UDP

O exemplo de Transporte UDP demonstra como implementar UDP unicast e multicast como um transporte personalizado do Windows Communication Foundation (WCF). O exemplo descreve o procedimento recomendado para criar um transporte personalizado no WCF, usando a estrutura de canal e seguindo as práticas recomendadas do WCF. As etapas para criar um transporte personalizado são as seguintes:

  1. Decida qual dos padrões de troca de mensagens do canal (IOutputChannel, IInputChannel, IDuplexChannel, IRequestChannel ou IReplyChannel) seu ChannelFactory e ChannelListener suportarão. Em seguida, decida se você suportará as variações de sessão dessas interfaces.

  2. Crie uma fábrica de canais e um ouvinte que suportem seu padrão de troca de mensagens.

  3. Certifique-se de que todas as exceções específicas da rede sejam normalizadas para a classe derivada apropriada de CommunicationException.

  4. Adicione um <elemento binding> que adiciona o transporte personalizado a uma pilha de canais. Para obter mais informações, consulte Adicionando um elemento de vinculação.

  5. Adicione uma seção de extensão de elemento de vinculação para expor o novo elemento de vinculação ao sistema de configuração.

  6. Adicione extensões de metadados para comunicar recursos a outros pontos de extremidade.

  7. Adicione uma associação que pré-configure uma pilha de elementos de vinculação de acordo com um perfil bem definido. Para obter mais informações, consulte Adicionando uma ligação padrão.

  8. Adicione uma seção de vinculação e um elemento de configuração de vinculação para expor a associação ao sistema de configuração. Para obter mais informações, consulte Adicionando suporte à configuração.

Padrões de troca de mensagens

A primeira etapa para escrever um transporte personalizado é decidir quais padrões de troca de mensagens (MEPs) são necessários para o transporte. Há três deputados ao Parlamento Europeu à escolha:

  • Datagrama (IInputChannel/IOutputChannel)

    Ao usar um MEP de datagrama, um cliente envia uma mensagem usando uma troca "disparar e esquecer". Uma troca de fogo e esquecimento é aquela que requer confirmação fora da banda de entrega bem-sucedida. A mensagem pode ser perdida em trânsito e nunca chegar ao serviço. Se a operação de envio for concluída com êxito no final do cliente, isso não garante que o ponto de extremidade remoto tenha recebido a mensagem. O datagrama é um bloco de construção fundamental para mensagens, pois você pode criar seus próprios protocolos sobre ele, incluindo protocolos confiáveis e protocolos seguros. Os canais de datagrama do cliente implementam a interface e os IOutputChannel canais de datagrama de serviço implementam a IInputChannel interface.

  • Solicitação-Resposta (IRequestChannel/IReplyChannel)

    Neste deputado, é enviada uma mensagem e recebida uma resposta. O padrão consiste em pares solicitação-resposta. Exemplos de chamadas de solicitação-resposta são chamadas de procedimento remoto (RPC) e GETs do navegador. Este padrão também é conhecido como Half-Duplex. Neste MEP, os canais do cliente implementam IRequestChannel e os canais de atendimento implementam IReplyChannel.

  • Duplex (IDuplexChannel)

    O duplex MEP permite que um número arbitrário de mensagens sejam enviadas por um cliente e recebidas em qualquer ordem. O MEP duplex é como uma conversa telefónica, em que cada palavra dita é uma mensagem. Como ambos os lados podem enviar e receber neste MEP, a interface implementada pelo cliente e canais de atendimento é IDuplexChannel.

Cada um destes deputados também pode apoiar as sessões. A funcionalidade adicional fornecida por um canal com reconhecimento de sessão é que ele correlaciona todas as mensagens enviadas e recebidas em um canal. O padrão Request-Response é uma sessão autônoma de duas mensagens, pois a solicitação e a resposta estão correlacionadas. Por outro lado, o padrão Solicitação-Resposta que suporta sessões implica que todos os pares de solicitação/resposta nesse canal estão correlacionados entre si. Isso oferece um total de seis MEPs — Datagrama, Solicitação-Resposta, Duplex, Datagrama com sessões, Solicitação-Resposta com sessões e Duplex com sessões — para escolher.

Nota

Para o transporte UDP, o único MEP suportado é o Datagram, porque o UDP é inerentemente um protocolo "fire and forget".

O ICommunicationObject e o ciclo de vida do objeto WCF

O WCF tem uma máquina de estado comum que é usada para gerenciar o ciclo de vida de objetos como IChannel, IChannelFactorye IChannelListener que são usados para comunicação. Há cinco estados em que esses objetos de comunicação podem existir. Esses estados são representados pela CommunicationState enumeração e são os seguintes:

  • Criado: Este é o estado de a ICommunicationObject quando ele é instanciado pela primeira vez. Nenhuma entrada/saída (E/S) ocorre nesse estado.

  • Abertura: Os objetos transitam para este estado quando Open são chamados. Neste ponto, as propriedades tornam-se imutáveis e a entrada/saída pode começar. Essa transição é válida somente a partir do estado Criado.

  • Aberto: os objetos transitam para esse estado quando o processo aberto é concluído. Essa transição é válida somente a partir do estado Abertura. Neste ponto, o objeto é totalmente utilizável para transferência.

  • Fechamento: Os objetos transitam para esse estado quando Close é chamado para um desligamento normal. Essa transição é válida somente a partir do estado Aberto.

  • Fechado: No estado Fechado, os objetos não são mais utilizáveis. Em geral, a maioria das configurações ainda está acessível para inspeção, mas nenhuma comunicação pode ocorrer. Este estado equivale a ser descartado.

  • Com defeito: no estado com defeito, os objetos são acessíveis à inspeção, mas não são mais utilizáveis. Quando ocorre um erro não recuperável, o objeto transita para esse estado. A única transição válida deste estado é para o Closed estado.

Há eventos que disparam para cada transição de estado. O Abort método pode ser chamado a qualquer momento e faz com que o objeto faça a transição imediata de seu estado atual para o estado Fechado. A chamada Abort encerra qualquer trabalho inacabado.

Fábrica de canais e ouvinte de canais

O próximo passo para escrever um transporte personalizado é criar uma implementação de para canais de cliente e de para canais de IChannelFactoryIChannelListener serviço. A camada de canal usa um padrão de fábrica para construir canais. O WCF fornece auxiliares de classe base para esse processo.

Neste exemplo, a implementação de fábrica está contida em UdpChannelFactory.cs e a implementação de ouvinte está contida em UdpChannelListener.cs. As IChannel implementações estão em UdpOutputChannel.cs e UdpInputChannel.cs.

A fábrica de canais UDP

O UdpChannelFactory deriva de ChannelFactoryBase. O exemplo substitui GetProperty para fornecer acesso à versão da mensagem do codificador de mensagens. O exemplo também substitui OnClose para que possamos derrubar nossa instância de quando a máquina de BufferManager estado faz a transição.

O canal de saída UDP

Os UdpOutputChannel implementos IOutputChannel. O construtor valida os argumentos e constrói um objeto de destino EndPoint com base no EndpointAddress que é passado.

this.socket = new Socket(this.remoteEndPoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp);

O canal pode ser fechado graciosamente ou sem graça. Se o canal estiver fechado normalmente, o soquete será fechado e uma chamada será feita para o método de classe OnClose base. Se isso gerar uma exceção, a infraestrutura chamará Abort para garantir que o canal seja limpo.

this.socket.Close(0);

Em seguida, implementamos Send() e BeginSend()/EndSend(). Esta divide-se em duas secções principais. Primeiro, serializamos a mensagem em uma matriz de bytes.

ArraySegment<byte> messageBuffer = EncodeMessage(message);

Em seguida, enviamos os dados resultantes no fio.

this.socket.SendTo(messageBuffer.Array, messageBuffer.Offset, messageBuffer.Count, SocketFlags.None, this.remoteEndPoint);

O UdpChannelListener

O UdpChannelListener que o exemplo implementa deriva da ChannelListenerBase classe. Ele usa um único soquete UDP para receber datagramas. O OnOpen método recebe dados usando o soquete UDP em um loop assíncrono. Os dados são então convertidos em mensagens usando o Message Encoding Framework.

message = MessageEncoderFactory.Encoder.ReadMessage(new ArraySegment<byte>(buffer, 0, count), bufferManager);

Como o mesmo canal de datagrama representa mensagens que chegam de várias fontes, o UdpChannelListener é um ouvinte singleton. Há, no máximo, um ativo IChannel associado a esse ouvinte de cada vez. A amostra gera outra somente se um canal que é retornado pelo AcceptChannel método é posteriormente descartado. Quando uma mensagem é recebida, ela é enfileirada nesse canal singleton.

UdpInputChannel

A UdpInputChannel classe implementa IInputChannel. Ele consiste em uma fila de mensagens de entrada que é preenchida pelo soquete UdpChannelListenerdo . Essas mensagens são retiradas da IInputChannel.Receive fila pelo método.

Adicionando um elemento de vinculação

Agora que as fábricas e os canais são construídos, devemos expô-los ao tempo de execução do ServiceModel por meio de uma ligação. Uma associação é uma coleção de elementos de ligação que representa a pilha de comunicação associada a um endereço de serviço. Cada elemento na pilha é representado por um <elemento de ligação> .

Na amostra, o elemento de ligação é UdpTransportBindingElement, que deriva de TransportBindingElement. Ele substitui os seguintes métodos para construir as fábricas associadas à nossa vinculação.

public IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)
{
    return (IChannelFactory<TChannel>)(object)new UdpChannelFactory(this, context);
}

public IChannelListener<TChannel> BuildChannelListener<TChannel>(BindingContext context)
{
    return (IChannelListener<TChannel>)(object)new UdpChannelListener(this, context);
}

Ele também contém membros para clonar e retornar nosso BindingElement esquema (soap.udp).

Adicionando suporte a metadados para um elemento de vinculação de transporte

Para integrar o nosso transporte no sistema de metadados, temos de apoiar tanto a importação como a exportação de políticas. Isso nos permite gerar clientes de nossa vinculação por meio da ServiceModel Metadata Utility Tool (Svcutil.exe).

Adicionando suporte a WSDL

O elemento de ligação de transporte em uma associação é responsável por exportar e importar informações de endereçamento em metadados. Ao usar uma associação SOAP, o elemento de ligação de transporte também deve exportar um URI de transporte correto nos metadados.

Exportação WSDL

Para exportar informações de endereçamento, o UdpTransportBindingElement implementa a IWsdlExportExtension interface. O ExportEndpoint método adiciona as informações de endereçamento corretas à porta WSDL.

if (context.WsdlPort != null)
{
    AddAddressToWsdlPort(context.WsdlPort, context.Endpoint.Address, encodingBindingElement.MessageVersion.Addressing);
}

A UdpTransportBindingElement implementação do método também exporta ExportEndpoint um URI de transporte quando o ponto de extremidade usa uma associação SOAP.

WsdlNS.SoapBinding soapBinding = GetSoapBinding(context, exporter);
if (soapBinding != null)
{
    soapBinding.Transport = UdpPolicyStrings.UdpNamespace;
}

Importação WSDL

Para estender o sistema de importação WSDL para lidar com a importação dos endereços, devemos adicionar a seguinte configuração ao arquivo de configuração para Svcutil.exe conforme mostrado no arquivo Svcutil.exe.config.

<configuration>
  <system.serviceModel>
    <client>
      <metadata>
        <policyImporters>
          <extension type=" Microsoft.ServiceModel.Samples.UdpBindingElementImporter, UdpTransport" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>
</configuration>

Ao executar Svcutil.exe, há duas opções para obter Svcutil.exe para carregar as extensões de importação WSDL:

  1. Aponte Svcutil.exe para o nosso arquivo de configuração usando o /SvcutilConfig:<file>.

  2. Adicione a seção de configuração ao Svcutil.exe.config no mesmo diretório que Svcutil.exe.

O UdpBindingElementImporter tipo implementa a IWsdlImportExtension interface. O ImportEndpoint método importa o endereço da porta WSDL.

BindingElementCollection bindingElements = context.Endpoint.Binding.CreateBindingElements();
TransportBindingElement transportBindingElement = bindingElements.Find<TransportBindingElement>();
if (transportBindingElement is UdpTransportBindingElement)
{
    ImportAddress(context);
}

Adicionando suporte a políticas

O elemento de vinculação personalizado pode exportar asserções de política na associação WSDL para um ponto de extremidade de serviço para expressar os recursos desse elemento de vinculação.

Exportação de políticas

O UdpTransportBindingElement tipo implementa IPolicyExportExtension para adicionar suporte para exportar política. Como resultado, System.ServiceModel.MetadataExporter inclui UdpTransportBindingElement na geração de política para qualquer vinculação que a inclua.

No IPolicyExportExtension.ExportPolicy, adicionamos uma asserção para UDP e outra asserção se estivermos no modo multicast. Isso ocorre porque o modo de multicast afeta como a pilha de comunicação é construída e, portanto, deve ser coordenada entre ambos os lados.

ICollection<XmlElement> bindingAssertions = context.GetBindingAssertions();
XmlDocument xmlDocument = new XmlDocument();
bindingAssertions.Add(xmlDocument.CreateElement(
UdpPolicyStrings.Prefix, UdpPolicyStrings.TransportAssertion, UdpPolicyStrings.UdpNamespace));
if (Multicast)
{
    bindingAssertions.Add(xmlDocument.CreateElement(
        UdpPolicyStrings.Prefix,
        UdpPolicyStrings.MulticastAssertion,
        UdpPolicyStrings.UdpNamespace));
}

Como os elementos de vinculação de transporte personalizados são responsáveis por lidar com o endereçamento, a IPolicyExportExtension implementação no deve também lidar com a UdpTransportBindingElement exportação das asserções de política WS-Addressing apropriadas para indicar a versão do WS-Addressing que está sendo usada.

AddWSAddressingAssertion(context, encodingBindingElement.MessageVersion.Addressing);

Importação de políticas

Para estender o sistema de importação de política, devemos adicionar a seguinte configuração ao arquivo de configuração para Svcutil.exe conforme mostrado no arquivo Svcutil.exe.config.

<configuration>
  <system.serviceModel>
    <client>
      <metadata>
        <policyImporters>
          <extension type=" Microsoft.ServiceModel.Samples.UdpBindingElementImporter, UdpTransport" />
        </policyImporters>
      </metadata>
    </client>
  </system.serviceModel>
</configuration>

Em seguida, implementamos IPolicyImporterExtension a partir de nossa classe registrada (UdpBindingElementImporter). No ImportPolicy(), examinamos as asserções em nosso namespace e processamos as que geram o transporte e verificamos se ele é multicast. Também devemos remover as asserções que lidamos da lista de asserções vinculativas. Novamente, ao executar Svcutil.exe, há duas opções de integração:

  1. Aponte Svcutil.exe para o nosso arquivo de configuração usando o /SvcutilConfig:<file>.

  2. Adicione a seção de configuração ao Svcutil.exe.config no mesmo diretório que Svcutil.exe.

Adicionando uma vinculação padrão

Nosso elemento de ligação pode ser usado das duas maneiras a seguir:

  • Através de uma associação personalizada: uma associação personalizada permite que o usuário crie sua própria associação com base em um conjunto arbitrário de elementos de ligação.

  • Usando uma ligação fornecida pelo sistema que inclui nosso elemento de ligação. O WCF fornece várias dessas ligações definidas pelo sistema, como BasicHttpBinding, NetTcpBindinge WsHttpBinding. Cada uma dessas ligações está associada a um perfil bem definido.

O exemplo implementa a vinculação de perfil em SampleProfileUdpBinding, que deriva de Binding. O SampleProfileUdpBinding contém até quatro elementos de ligação dentro dele: UdpTransportBindingElement, TextMessageEncodingBindingElement CompositeDuplexBindingElement, e ReliableSessionBindingElement.

public override BindingElementCollection CreateBindingElements()
{
    BindingElementCollection bindingElements = new BindingElementCollection();
    if (ReliableSessionEnabled)
    {
        bindingElements.Add(session);
        bindingElements.Add(compositeDuplex);
    }
    bindingElements.Add(encoding);
    bindingElements.Add(transport);
    return bindingElements.Clone();
}

Adicionando um importador de vinculação padrão personalizado

Svcutil.exe e o WsdlImporter tipo, por padrão, reconhece e importa ligações definidas pelo sistema. Caso contrário, a associação será importada como uma CustomBinding instância. Para permitir Svcutil.exe e importar WsdlImporter o SampleProfileUdpBindingUdpBindingElementImporter também atua como um importador de vinculação padrão personalizado.

Um importador de vinculação padrão personalizado implementa o ImportEndpoint método na IWsdlImportExtension interface para examinar a CustomBinding instância importada de metadados para ver se ela poderia ter sido gerada por uma associação padrão específica.

if (context.Endpoint.Binding is CustomBinding)
{
    Binding binding;
    if (transportBindingElement is UdpTransportBindingElement)
    {
        //if TryCreate is true, the CustomBinding will be replace by a SampleProfileUdpBinding in the
        //generated config file for better typed generation.
        if (SampleProfileUdpBinding.TryCreate(bindingElements, out binding))
        {
            binding.Name = context.Endpoint.Binding.Name;
            binding.Namespace = context.Endpoint.Binding.Namespace;
            context.Endpoint.Binding = binding;
        }
    }
}

Geralmente, a implementação de um importador de vinculação padrão personalizado envolve a verificação das propriedades dos elementos de vinculação importados para verificar se apenas as propriedades que poderiam ter sido definidas pela associação padrão foram alteradas e todas as outras propriedades são seus padrões. Uma estratégia básica para implementar um importador de vinculação padrão é criar uma instância da associação padrão, propagar as propriedades dos elementos de vinculação para a instância de vinculação padrão que a vinculação padrão suporta e comparar os elementos de ligação da associação padrão com os elementos de vinculação importados.

Adicionando suporte à configuração

Para expor nosso transporte através da configuração, devemos implementar duas seções de configuração. O primeiro é um BindingElementExtensionElement para UdpTransportBindingElement. Isso é para que CustomBinding as implementações possam fazer referência ao nosso elemento de vinculação. O segundo é um para o Configuration nosso SampleProfileUdpBinding.

Elemento de extensão de elemento de ligação

A seção UdpTransportElement é uma BindingElementExtensionElement que expõe UdpTransportBindingElement ao sistema de configuração. Com algumas substituições básicas, definimos nosso nome de seção de configuração, o tipo de nosso elemento de vinculação e como criar nosso elemento de ligação. Podemos então registrar nossa seção de extensão em um arquivo de configuração, conforme mostrado no código a seguir.

<configuration>
  <system.serviceModel>
    <extensions>
      <bindingElementExtensions>
        <add name="udpTransport" type="Microsoft.ServiceModel.Samples.UdpTransportElement, UdpTransport" />
      </bindingElementExtensions>
    </extensions>
  </system.serviceModel>
</configuration>

A extensão pode ser referenciada a partir de ligações personalizadas para usar UDP como transporte.

<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
       <binding configurationName="UdpCustomBinding">
         <udpTransport/>
       </binding>
      </customBinding>
    </bindings>
  </system.serviceModel>
</configuration>

Secção Vinculativa

A seção SampleProfileUdpBindingCollectionElement é uma StandardBindingCollectionElement que expõe SampleProfileUdpBinding ao sistema de configuração. A maior parte da implementação é delegada ao , que deriva SampleProfileUdpBindingConfigurationElementde StandardBindingElement. O SampleProfileUdpBindingConfigurationElement tem propriedades que correspondem às propriedades em SampleProfileUdpBindinge funções a serem mapeadas a ConfigurationElement partir da associação. Finalmente, substitua o OnApplyConfiguration método em nosso SampleProfileUdpBinding, conforme mostrado no código de exemplo a seguir.

protected override void OnApplyConfiguration(string configurationName)
{
    if (binding == null)
        throw new ArgumentNullException("binding");

    if (binding.GetType() != typeof(SampleProfileUdpBinding))
    {
        throw new ArgumentException(string.Format(CultureInfo.CurrentCulture,
            "Invalid type for binding. Expected type: {0}. Type passed in: {1}.",
            typeof(SampleProfileUdpBinding).AssemblyQualifiedName,
            binding.GetType().AssemblyQualifiedName));
    }
    SampleProfileUdpBinding udpBinding = (SampleProfileUdpBinding)binding;

    udpBinding.OrderedSession = this.OrderedSession;
    udpBinding.ReliableSessionEnabled = this.ReliableSessionEnabled;
    udpBinding.SessionInactivityTimeout = this.SessionInactivityTimeout;
    if (this.ClientBaseAddress != null)
        udpBinding.ClientBaseAddress = ClientBaseAddress;
}

Para registrar esse manipulador no sistema de configuração, adicionamos a seção a seguir ao arquivo de configuração relevante.

<configuration>
  <configSections>
     <sectionGroup name="system.serviceModel">
        <sectionGroup name="bindings">
          <section name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" />
        </sectionGroup>
     </sectionGroup>
  </configSections>
</configuration>

Em seguida, ele pode ser referenciado na seção de configuração serviceModel.

<configuration>
  <system.serviceModel>
    <client>
      <endpoint configurationName="calculator"
                address="soap.udp://localhost:8001/"
                bindingConfiguration="CalculatorServer"
                binding="sampleProfileUdpBinding"
                contract= "Microsoft.ServiceModel.Samples.ICalculatorContract">
      </endpoint>
    </client>
  </system.serviceModel>
</configuration>

O Serviço de Teste UDP e o Cliente

O código de teste para usar esse transporte de exemplo está disponível nos diretórios UdpTestService e UdpTestClient. O código de serviço consiste em dois testes — um teste configura ligações e pontos de extremidade do código e o outro faz isso por meio da configuração. Ambos os testes usam dois parâmetros. Um ponto de extremidade usa o SampleUdpProfileBinding conjunto com< reliableSession> como true. O outro ponto de extremidade usa uma associação personalizada com UdpTransportBindingElement. Isso é equivalente a usar SampleUdpProfileBinding com <reliableSession> definido como false. Ambos os testes criam um serviço, adicionam um ponto de extremidade para cada ligação, abrem o serviço e aguardam que o usuário pressione ENTER antes de fechar o serviço.

Ao iniciar o aplicativo de teste de serviço, você verá a saída a seguir.

Testing Udp From Code.
Service is started from code...
Press <ENTER> to terminate the service and start service from config...

Em seguida, você pode executar o aplicativo cliente de teste nos pontos de extremidade publicados. O aplicativo de teste cliente cria um cliente para cada ponto de extremidade e envia cinco mensagens para cada ponto de extremidade. A saída a seguir está no cliente.

Testing Udp From Imported Files Generated By SvcUtil.
0
3
6
9
12
Press <ENTER> to complete test.

A seguir está a saída completa no serviço.

Service is started from code...
Press <ENTER> to terminate the service and start service from config...
Hello, world!
Hello, world!
Hello, world!
Hello, world!
Hello, world!
   adding 0 + 0
   adding 1 + 2
   adding 2 + 4
   adding 3 + 6
   adding 4 + 8

Para executar o aplicativo cliente em pontos de extremidade publicados usando a configuração, pressione ENTER no serviço e execute o cliente de teste novamente. Você deve ver a seguinte saída no serviço.

Testing Udp From Config.
Service is started from config...
Press <ENTER> to terminate the service and exit...

Executar o cliente novamente produz o mesmo que os resultados anteriores.

Para regenerar o código do cliente e a configuração usando Svcutil.exe, inicie o aplicativo de serviço e execute o seguinte Svcutil.exe a partir do diretório raiz do exemplo.

svcutil http://localhost:8000/udpsample/ /reference:UdpTransport\bin\UdpTransport.dll /svcutilConfig:svcutil.exe.config

Observe que Svcutil.exe não gera a configuração de extensão de vinculação para o SampleProfileUdpBinding, portanto, você deve adicioná-lo manualmente.

<configuration>
  <system.serviceModel>
    <extensions>
      <!-- This was added manually because svcutil.exe does not add this extension to the file -->
      <bindingExtensions>
        <add name="sampleProfileUdpBinding" type="Microsoft.ServiceModel.Samples.SampleProfileUdpBindingCollectionElement, UdpTransport" />
      </bindingExtensions>
    </extensions>
  </system.serviceModel>
</configuration>

Para configurar, compilar e executar o exemplo

  1. Para criar a solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.

  2. Para executar o exemplo em uma configuração de máquina única ou cruzada, siga as instruções em Executando os exemplos do Windows Communication Foundation.

  3. Consulte a seção anterior "O serviço de teste UDP e o cliente".