Compartilhar via


Transporte: UDP

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

  1. Decida a qual dos Padrões de Troca de Mensagem do canal (IOutputChannel, IInputChannel, IDuplexChannel, IRequestChannel ou IReplyChannel) seu ChannelFactory e ChannelListener darão suporte. Em seguida, decida se você dará suporte às variações com sessão dessas interfaces.

  2. Criar uma fábrica de canais e um ouvinte que dê suporte ao seu padrão de troca de mensagens.

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

  4. Adicione um elemento de <associação> que acrescenta o transporte personalizado a uma pilha de canais. Para obter mais informações, consulte Adição de um elemento de associação.

  5. Adicione uma seção de extensão de elemento de associação para expor o novo elemento de associaçã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é-configura uma pilha de elementos de associação de acordo com um perfil bem definido. Para obter mais informações, consulte Adição de um elemento de associação.

  8. Adicione uma seção de associação e um elemento de configuração de associaçã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 na gravação de um transporte personalizado é decidir quais MEPs (Padrões de Troca de Mensagens) são necessários para o transporte. Há três MEPs a escolher:

  • Datagrama (IInputChannel/IOutputChannel)

    Ao usar um MEP de datagrama, um cliente envia uma mensagem usando uma troca "disparar e esquecer". Uma troca do tipo disparar e esquecer é aquela que requer confirmação de entrega bem-sucedida fora da faixa. 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 na extremidade do cliente, não haverá garantia de 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 além dele, incluindo protocolos confiáveis e protocolos seguros. Os canais de datagrama do cliente implementam a interface IOutputChannel e os canais de datagrama de serviço implementam a interface IInputChannel.

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

    Nesse MEP, uma mensagem é enviada e uma resposta é recebida. O padrão consiste em pares de solicitação-resposta. Exemplos de chamadas de solicitação-resposta são chamadas de procedimento remoto (RPC) e GETs do navegador. Esse padrão também é conhecido como meio duplex. Nesse MEP, os canais do cliente implementam IRequestChannel e os canais de serviço implementam IReplyChannel.

  • Duplex (IDuplexChannel)

    O MEP duplex permite que um número arbitrário de mensagens seja enviado por um cliente e recebido em qualquer ordem. O MEP duplex é como uma conversa telefônica, onde cada palavra que está sendo falada é uma mensagem. Como ambos os lados podem enviar e receber nesse MEP, a interface implementada pelos canais de cliente e de serviço é IDuplexChannel.

Cada um desses MEPs também pode dar suporte a sessões. A funcionalidade adicionada fornecida por um canal com reconhecimento de sessão é que ele correlaciona todas as mensagens enviadas e recebidas em um canal. O padrão solicitação-resposta é 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 dá suporte a sessões implica que todos os pares de solicitação/resposta nesse canal estão correlacionados entre si. Isso fornece 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.

Observação

Para o transporte UDP, o único MEP com suporte é o Datagrama, pois o UDP é inerentemente um protocolo "disparar e esquecer".

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 enumeração CommunicationState e são os seguintes:

  • Criado: esse é o estado de um ICommunicationObject quando se cria uma instância para ele pela primeira vez. Nenhuma entrada/saída (E/S) ocorre nesse estado.

  • Abertura: os objetos são transferidos para esse estado quando Open é chamado. Nesse ponto, as propriedades são imutáveis e a entrada/saída pode começar. Essa transição é válida somente do estado Criado.

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

  • Fechamento: os objetos são transferidos para esse estado quando Close é chamado para um desligamento normal. Essa transição é válida somente do estado Aberto.

  • Fechado: os objetos de estado fechado 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. Esse estado é o equivalente a ser descartado.

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

Há eventos que disparam em cada transição de estado. O método Abort pode ser chamado a qualquer momento e faz com que o objeto faça a transição imediatamente de seu estado atual para o estado Fechado. A chamada de Abort termina qualquer trabalho não concluído.

Fábrica de canais e Ouvinte de Canal

A próxima etapa na gravação de um transporte personalizado é criar uma implementação de IChannelFactory para canais de cliente e de IChannelListener para canais de serviço. A camada de canal usa um padrão de fábrica na construção de canais. O WCF fornece auxiliares de classe base para esse processo.

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

A Fábrica de Canais UDP

A UdpChannelFactory deriva de ChannelFactoryBase. A amostra substitui GetProperty para fornecer acesso à versão da mensagem do codificador de mensagens. A amostra também substitui OnClose para que possamos derrubar nossa instância de BufferManager, quando a máquina de 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 EndPoint de destino com base no EndpointAddress que é passado.

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

O canal pode ser fechado de forma coordenada ou descoordenada. Se o canal for fechado de forma coordenada, o soquete será fechado e uma chamada será feita para o método OnClose da classe 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(). Isso se divide em duas seções principais. Primeiro, serializamos a mensagem em uma matriz de bytes.

ArraySegment<byte> messageBuffer = EncodeMessage(message);

Em seguida, enviamos os dados resultantes na rede.

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

O UdpChannelListener

O UdpChannelListener que a amostra implementa deriva da classe ChannelListenerBase. Ele usa um único soquete UDP para receber datagramas. O método OnOpen recebe dados usando o soquete UDP em um loop assíncrono. Em seguida, os dados são convertidos em mensagens usando a Estrutura de Codificação de Mensagens.

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 IChannel ativo associado a esse ouvinte de cada vez. A amostra somente gerará outra, se um canal retornado pelo métodoAcceptChannel for descartado posteriormente. Quando uma mensagem é recebida, ela é enfileirada neste canal singleton.

UdpInputChannel

A classe UdpInputChannel implementa o IInputChannel. Ele consiste em uma fila de mensagens de entrada que é propagada pelo soquete do UdpChannelListener. Essas mensagens são removidas da fila pelo método IInputChannel.Receive.

Adicionar um elemento de associação

Agora que as fábricas e os canais foram criados, eles devem ser expostos ao runtime do ServiceModel por meio de uma associação. Uma associação é uma coleção de elementos de associaçã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 <associação>.

Na amostra, o elemento de associação é UdpTransportBindingElement, que deriva de TransportBindingElement. Ele substitui os métodos a seguir para criar as fábricas associadas à nossa associaçã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 o BindingElement e retornar nosso esquema (soap.udp).

Adição de Suporte a Metadados para um Elemento de Associação

Para integrar um canal ao sistema de metadados, devemos permitir a importação e a exportação de política. Isso nos permite gerar clientes de nossa associação por meio da Ferramenta de Utilitário de Metadados do ServiceModel (Svcutil.exe).

Adição de Suporte a WSDL

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

Exportação do WSDL

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

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

A implementação UdpTransportBindingElement do método ExportEndpoint também exporta 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 do WSDL

Para estender o sistema de importação do 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 mostra o 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 o Svcutil.exe, há duas opções para fazer com que o Svcutil.exe carregue as extensões de importação do WSDL:

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

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

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

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

Adição de suporte a políticas

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

Exportação de Política

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

Em IPolicyExportExtension.ExportPolicy, adicionamos uma declaração para UDP e outra declaração se estivermos no modo multicast. Isso ocorre porque o modo multicast afeta a forma como a pilha de comunicação é construída e, portanto, deve ser coordenado 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 associação de transporte personalizada são responsáveis por lidar com o endereçamento, a implementação IPolicyExportExtension no UdpTransportBindingElement também deve lidar com a exportação das devidas declarações de política de WS-Addressing, para indicar a versão do WS-Addressing usado.

AddWSAddressingAssertion(context, encodingBindingElement.MessageVersion.Addressing);

Importação de Política

Para estender o sistema de importação de política, adicionamos a seguinte configuração ao arquivo de configuração para Svcutil.exe, conforme mostra o 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 por meio de nossa classe registrada (UdpBindingElementImporter). Em ImportPolicy(), analisamos as declarações em nosso namespace e processamos as que geram o transporte e verificamos se é multicast. Também devemos remover as declarações que manipulamos da lista de declarações de associação. Novamente, ao executar Svcutil.exe, há duas opções para integração:

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

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

Adicionando uma associação padrão

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

  • Por meio de uma associação personalizada: uma associação personalizada permite que o usuário crie uma associação própria com base em um conjunto arbitrário de elementos de associação.

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

A amostra implementa a associação de perfil em SampleProfileUdpBinding, que deriva de Binding. A SampleProfileUdpBinding contém até quatro elementos de associação dentro dela: UdpTransportBindingElement, TextMessageEncodingBindingElement CompositeDuplexBindingElemente 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();
}

Adição de um Importador de Associação Padrão Personalizada

O Svcutil.exe e o tipo WsdlImporter, por padrão, reconhecem e importam associações definidas pelo sistema. Caso contrário, a associação será importada como instância CustomBinding. Habilitar o Svcutil.exe e o WsdlImporter para importar a SampleProfileUdpBinding, o UdpBindingElementImporter também atua como importador de associação padrão personalizada.

Um importador de associação padrão personalizada implementa o método ImportEndpoint na interface IWsdlImportExtension para examinar a instância CustomBinding importada dos 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;
        }
    }
}

Em geral, a implementação de um importador de associação padrão personalizada envolve verificar as propriedades dos elementos de associação importados, para ver se apenas as propriedades que poderiam ter sido definidas pela associação padrão foram alteradas e se todas as outras propriedades são os padrões. Uma estratégia básica para implementar um importador de associação padrão é criar uma instância da associação padrão, propagar as propriedades dos elementos de associação para a instância de associação padrão compatível com a associação padrão e comparar os elementos de associação da associação padrão com os elementos de associação importados.

Adição de Suporte à Configuração

Para expor nosso transporte por meio da configuração, devemos implementar duas seções de configuração. A primeira é um BindingElementExtensionElement para o UdpTransportBindingElement. Isso serve para que as implementações de CustomBinding possam referenciar nosso elemento de associação. O segundo é uma Configuration para a nossa SampleProfileUdpBinding.

Elemento de Extensão do Elemento de Associação

A seção UdpTransportElement é um BindingElementExtensionElement que expõe o UdpTransportBindingElement ao sistema de configuração. Com algumas substituições básicas, definimos o nome da seção de configuração, o tipo do elemento de associação e como criar nosso elemento de associação. Em seguida, podemos registrar nossa seção de extensão em um arquivo de configuração, conforme mostra o 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 em associações personalizadas para usar o UDP como o transporte.

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

Seção de associação

A seção SampleProfileUdpBindingCollectionElement é um StandardBindingCollectionElement que expõe a SampleProfileUdpBinding ao sistema de configuração. A maior parte da implementação é delegada ao SampleProfileUdpBindingConfigurationElement, que deriva do StandardBindingElement. O SampleProfileUdpBindingConfigurationElement tem propriedades que correspondem às propriedades em SampleProfileUdpBinding e funções a serem mapeadas a partir da associação ConfigurationElement. Por fim, substitua o método OnApplyConfiguration em nossa SampleProfileUdpBinding, conforme mostra o 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 com o 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 poderá ser referenciado a partir da 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 e o cliente de teste do UDP

O código de teste para usar esse transporte de amostra está disponível nos diretórios UdpTestService e UdpTestClient. O código de serviço consiste em dois testes: um teste configura associações e pontos de extremidade a partir do código e o outro faz isso por meio da configuração. Ambos os testes usam dois pontos de extremidade. Um ponto de extremidade usa a SampleUdpProfileBinding em conjunto com <reliableSession> configurada como true. O outro ponto de extremidade usa uma associação personalizada com UdpTransportBindingElement. Isso equivale ao uso da SampleUdpProfileBinding com <reliableSession> definida como false. Ambos os testes criam um serviço, adicionam um ponto de extremidade para cada associação, abrem o serviço e aguardam o usuário pressionar ENTER antes de fechar o serviço.

Ao executar o aplicativo de teste de serviço, você verá a seguinte saída:

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 do 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 saída a seguir 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 e a configuração do cliente usando Svcutil.exe, inicie o aplicativo de serviço e execute o Svcutil.exe a seguir no diretório raiz da amostra.

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

Observe que, como o Svcutil.exe não gera a configuração de extensão de associação para a SampleProfileUdpBinding; você deve adicioná-la 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 compilar a solução, siga as instruções contidas em Como compilar as amostras do Windows Communication Foundation.

  2. Para executara amostra em uma configuração de computador único ou entre computadores, siga as instruções contidas em Como executar as amostras do Windows Communication Foundation.

  3. Consulte a seção anterior "O Serviço de Teste e o Cliente do UDP".