Partilhar via


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 de forma a comunicar capacidades 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 Adicionar 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 um método "enviar e esquecer". Uma troca de tipo "disparar e esquecer" é aquela que requer confirmação externa da 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 do lado do cliente, isso não garante que o endpoint 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 IOutputChannel e os canais de datagrama de serviço implementam a interface IInputChannel.

  • Request-Response (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, onde cada palavra falada é 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. Em contraste, o padrão de Request-Response 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, Request-Response com sessões e Duplex com sessões — para escolher.

Observação

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 do ICommunicationObject quando é 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 de 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 em 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 IChannelFactory para canais de cliente e de IChannelListener para canais de 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 desmontar a nossa instância de BufferManager quando a máquina de estados faz a transição.

O canal de saída UDP

O UdpOutputChannel implementa 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 de forma desajeitada. Se o canal for fechado de forma gentil, o soquete será encerrado e será feita uma chamada ao método da classe base OnClose. Caso isso gere 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 funciona como um ouvinte singleton. Há, no máximo, um elemento ativo IChannel associado a este 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. Consiste numa fila de receção de mensagens que é preenchida pelo socket UdpChannelListener. Essas mensagens são retiradas da fila pelo método IInputChannel.Receive.

Adicionando um elemento de vinculação

Agora que as fábricas e os canais estã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 o BindingElement e retornar o nosso esquema de endereçamento (soap.udp).

Adição de suporte a metadados para um elemento de ligaçã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 através 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 um URI de transporte ExportEndpoint 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 o Svcutil.exe, há duas opções para fazer com que Svcutil.exe carregue 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 para 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 tipo UdpTransportBindingElement implementa IPolicyExportExtension para adicionar suporte à exportação de políticas. 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 caso estejamos 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 ligação de transporte personalizados são responsáveis por lidar com o endereçamento, a IPolicyExportExtension implementação no UdpTransportBindingElement deve também lidar com a exportação das asserções de política WS-Addressing apropriadas para indicar a versão do WS-Addressing que está a ser utilizada.

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 para 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 que Svcutil.exe e o WsdlImporter importem o SampleProfileUdpBinding, o UdpBindingElementImporter também atua como um importador padrão de vinculaçã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 o nosso transporte através da configuração, é necessário implementar duas secçõ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 Configuration para o 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 SampleProfileUdpBindingConfigurationElement, que deriva de StandardBindingElement. O SampleProfileUdpBindingConfigurationElement tem propriedades que correspondem às propriedades em SampleProfileUdpBinding, e funções para mapear a partir da associação ConfigurationElement. 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>

Depois, pode ser referenciado na seção de configuração do 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 pontos finais. Um ponto de extremidade usa o SampleUdpProfileBinding com <reliableSession> configurado para 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, podes executar a aplicação cliente de teste contra os 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 seguinte está no cliente.

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

Segue o resultado completo do 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. Deve ver o seguinte resultado 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".