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, utilizando a estrutura de canal e seguindo as melhores práticas 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 darão suporte. Em seguida, decida se você dará suporte às variações com sessão dessas interfaces.

  2. Crie uma fábrica de canais e um ouvinte que dê suporte ao seu Padrão de Troca de Mensagens.

  3. Verifique se todas as exceções específicas da rede são normalizadas para a classe derivada apropriada de CommunicationException.

  4. Adicione um elemento de <binding> que acrescenta o transporte personalizado a uma pilha de canais. Para obter mais informações, consulte Adicionando 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 Adicionando uma associação padrã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 Como adicionar 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 deputados para 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 no lado do cliente, isso não garante que o ponto de extremidade remoto tenha recebido a mensagem. O datagram é um bloco de construção fundamental para mensagens, pois você pode criar seus próprios protocolos em cima 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 solicitação-resposta. Exemplos de chamadas de solicitação-resposta são RPC (chamadas de procedimento remoto) e GETs do navegador. Esse padrão também é conhecido como Half-Duplex. Neste MEP, os canais de 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 neste MEP, a interface implementada pelo cliente e pelos canais 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 Request-Response é uma sessão autônoma de duas mensagens, pois a solicitação e a resposta são correlacionadas. Por outro lado, o padrão Request-Response 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 um computador de estado comum que é usado 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: esse é o estado de um ICommunicationObject quando ele é instanciado pela primeira vez. Nenhuma E/S (entrada/saída) ocorre nesse estado.

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

  • Aberto: os objetos fazem a transição para esse estado quando o processo aberto é concluído. Essa transição é válida somente do estado de Abertura. Neste 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: nos 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 é equivalente a ser descartado.

  • Com falha: no estado com falha, 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 faz a transição para esse estado. A única transição válida desse 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 imediatamente de seu estado atual para o estado Fechado. A chamada Abort encerra qualquer trabalho inacabado.

Fábrica de canais e Ouvinte de Canal

A próxima etapa na escrita 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.

Neste exemplo, a implementação de fábrica está contida em UdpChannelFactory.cs e a implementação do ouvinte está contida no 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. 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

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 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 pela rede.

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. 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. O exemplo gerará outro somente se um canal retornado pelo AcceptChannel método for descartado posteriormente. Quando uma mensagem é recebida, ela é enfileirada neste canal singleton.

UdpInputChannel

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

Adicionando 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 vinculação é uma coleção de elementos de vinculação que representa a camada de comunicação associada a um endereço de serviço. Cada elemento na pilha é representado por um elemento <binding>.

No exemplo, 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 nosso transporte ao sistema de metadados, devemos dar suporte à importação e à exportação da política. Isso nos permite gerar clientes de nossa associação por meio da ServiceModel Metadata Utility Tool (Svcutil.exe).

Adicionando suporte ao WSDL

O elemento de associaçã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 associação de transporte também deve exportar um URI de transporte correto em metadados.

Exportação do WSDL

Para exportar informações de endereçamento, o UdpTransportBindingElement implementa a interface IWsdlExportExtension. 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 ExportEndpoint método 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 WSDL para lidar com a importação dos endereços, devemos adicionar a configuração a seguir 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 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 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 à política

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

O UdpTransportBindingElement tipo 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, 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 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 asserções que manipulamos da lista de asserções vinculativas. 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 para 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 sua própria associação 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 várias dessas associações definidas pelo sistema, como BasicHttpBinding, NetTcpBindinge WsHttpBinding. Cada uma dessas associações está associada a um perfil bem definido.

O exemplo implementa a associação de perfil em SampleProfileUdpBinding, que deriva de Binding. O SampleProfileUdpBinding contém até quatro elementos de associação dentro dele: 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

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

Um importador de associaçã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;
        }
    }
}

Em geral, a implementação de um importador de associação padrão personalizado envolve a verificação das propriedades dos elementos de associaçã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 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.

Adicionando 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 é para que CustomBinding as implementações 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 é 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 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 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 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 é 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 a serem mapeadas a partir da associação ConfigurationElement. Por fim, sobrescreva o método OnApplyConfiguration 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 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 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 associações e pontos de extremidade 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 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> configurada como false. Ambos os testes criam um serviço, adicionam um ponto de extremidade para cada vínculo, abrem o serviço e, em seguida, aguardam o usuário pressionar ENTER antes de fechar o serviço.

Ao iniciar o aplicativo de teste de serviço, você deverá 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 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 seguinte Svcutil.exe no 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 associação para o SampleProfileUdpBinding, portanto, 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 executar o exemplo em uma configuração única ou entre máquinas, siga as instruções em Executando os exemplos do Windows Communication Foundation.

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