Socket UDP

Esse é o décimo primeiro artigo da série Comunicação entre Processos, esse artigo demonstra o uso do IPC UDP.

Introdução

UDP significa User Datagram Protocol, é um protocolo que não precisa se conectar na máquina para qual se quer enviar a mensagem, ou seja, não há a necessidade do handshake presente no TCP necessário para a sua conexão, diante disso essa característica o torna muito mais rápido, sendo utilizados em aplicações que envolvem jogos online, streaming de video e video conferência. Diferente do TCP, o UDP não garante entrega de dados, a máquina que recebe a mensagem não informa que a mensagem foi recebida, dessa forma trazendo consigo a fama de ser um protocolo não confiável, o que acaba se tornando verdade quando as máquinas precisam usar a internet para se comunicarem(grandes distâncias) podendo entregar os pacotes fora de ordem, mas quando dentro de uma rede local acaba sendo uma opção melhor de utilização.

Datagrama UDP

O Protocolo UDP é um protocolo simples, devido a sua simplicidade garante uma melhor performance no processamento do protocolo. O UDP opera sobre o protocolo IP. A imagem a seguir demonstra como o protocolo UDP é encapsulado no protocolo IPv4:

ip udp

O Protocolo UDP possui 4 campos de cabeçalho(header) e o campo onde os dados são transmitidos chamado de payload:

udp

No Header possui um campo de 2 bytes para a porta de origem, um campo de 2 bytes para a porta de destino, mais um campo de 2 bytes para indicar o tamanho do payload a ser enviado e por fim um campo de 2 bytes de checksum. O tamanho máximo de bytes que o UDP pode transmitir é de 64kB(65535 bytes).

Systemcalls utilizados no UDP

Para criar uma aplicação utilizando UDP utilizamos com conjunto bem específico de funções, sendo elas descritas a seguir:

Cria um endpoint para estabelecer uma comunicação

Faz a junção da porta com o socket

Permite o envio de mensagens para o endpoint receptor

Permite receber mensagens do endpoint emissor

Preparação do Ambiente

Antes de apresentarmos o exemplo, primeiro precisaremos instalar algumas ferramentas para auxiliar na análise da comunicação. As ferramentas necessárias para esse artigo são o tcpdump e o netcat(nc), para instalá-las basta executar os comandos abaixo:

netcat

O netcat é uma ferramenta capaz de interagir com conexões UDP e TCP, podendo abrir conexões, ouvindo como um servidor, ou com cliente enviando mensagens para um servidor.

tcpdump

O tcpdump é uma ferramenta capaz de monitorar o tráfego de dados em uma dada interface como por exemplo eth0, com ele é possível analisar os pacotes que são recebido e enviados.

Implementação

Para demonstrar o uso desse IPC, iremos utilizar o modelo Cliente/Servidor, onde o processo Cliente(button_process) vai enviar uma mensagem para o Servidor(led_process) vai ler a mensagem, e verificar se corresponde com os comandos cadastrados internamente e aplicar o comando caso seja válido.

Biblioteca

A biblioteca criada permite uma fácil criação do servidor, sendo o servidor orientado a eventos, ou seja, fica aguardando as mensagens chegarem.

udp_server.h

Primeiramente criamos um callback responsável por eventos de recebimento, essa função será chamada quando houve esse evento.

Criamos também um contexto que armazena os parâmetros utilizados pelo servidor, sendo o socket para armazenar a instância criada, port que recebe o número que corresponde onde o serviço será disponibilizado, buffer que aponta para a memória alocada previamente pelo usuário, buffer_size o representa o tamanho do buffer e o callback para recepção da mensagem

Essa função inicializa o servidor com os parâmetros do contexto

Essa função aguarda uma mensagem enviada pelo cliente.

udp_server.c

No UDP_Server_Init definimos algumas váriaveis para auxiliar na inicialização do servidor, sendo uma variável booleana que representa o estado da inicialização do servidor, uma variável do tipo inteiro para habilitar o reuso da porta caso o servidor precise reiniciar e uma estrutura sockaddr_in que é usada para configurar o servidor para se comunicar através da rede.

Para realizar a inicialização é criado um dummy do while, para que quando houver falha em qualquer uma das etapas, irá sair da função com status de erro, nesse ponto verificamos se o contexto, o buffer e se o tamanho do buffer foi inicializado, sendo sua inicialização de responsabilidade do usuário

Criamos um endpoint com o perfil de se conectar via protocolo IPv4(AF_INET), do tipo datagram que caracteriza o UDP(SOCK_DGRAM), o último parâmetro pode ser 0 nesse caso.

Preenchemos a estrutura com parâmetros fornecidos pelo usuário como em qual porta que o serviço vai rodar.

Aqui permitimos o reuso do socket caso necessite reiniciar o serviço

Aplicamos as configurações ao socket criado e atribuimos true na variável status

Na função UDP_Server_Run declaramos algumas variáveis para receber as mensagens enviadas pelo cliente

Verificamos se o socket é válido e aguardamos uma mensagem do cliente, repassamos a mensagem para o callback realizar o tratamento de acordo com a aplicação do cliente, e retornamos o estado.

udp_client.h

Criamos também um contexto que armazena os parâmetros utilizados pelo cliente, sendo o socket para armazenar a instância criada, hostname é o ip que da máquina que vai enviar as mensagens e o port que recebe o número que corresponde qual o serviço deseja consumir

Inicializa o cliente com os parâmetros do descritor

Envia mensagem para o servidor baseado nos parâmetros do descritor.

udp_client.c

Na função UDP_Client_Init verificamos se o contexto foi iniciado, e configuramos o socket como UDP

Na função UDP_Client_Send definimos algumas variáveis para auxiliar na comunicação com o servidor, sendo uma variável booleana que representa o estado de envio para o servidor, uma estrutura sockaddr_in que é usada para configurar o servidor no qual será enviado as mensagens e uma variável de quantidade de dados enviados.

Parametrizamos a estrutura com os dados do servidor

Realizamos o envio da mensagem para o servidor

Aplicação é composta por três executáveis sendo eles:

  • launch_processes – é responsável por lançar os processos button_process e led_process através da combinação fork e exec
  • button_interface – é responsável por ler o GPIO em modo de leitura da Raspberry Pi e escrever o estado interno no arquivo
  • led_interface – é responsável por ler do arquivo o estado interno do botão e aplicar em um GPIO configurado como saída

launch_processes

No main criamos duas variáveis para armazenar o PID do button_process e do led_process, e mais duas variáveis para armazenar o resultado caso o exec venha a falhar.

Em seguida criamos um processo clone, se processo clone for igual a 0, criamos um array de strings com o nome do programa que será usado pelo exec, em caso o exec retorne, o estado do retorno é capturado e será impresso no stdout e aborta a aplicação. Se o exec for executado com sucesso o programa button_process será carregado.

O mesmo procedimento é repetido novamente, porém com a intenção de carregar o led_process.

button_interface

Definimos uma lista de comandos que iremos enviar

A implementação do Button_Run ficou simples, onde realizamos a inicialização do interface de botão e ficamos em loop aguardando o pressionamento do botão para alterar o estado da variável e enviar a mensagem para o servidor

led_interface

A implementação do LED_Run ficou simplificada, realizamos a inicialização da interface de LED, do servidor e ficamos em loop aguardando o recebimento de uma mensagem.

button_process

A parametrização do cliente fica por conta do processo de botão que inicializa o contexto o endereço do hostname, o serviço que deseja consumir, e assim passamos os argumentos para Button_Run iniciar o processo.

led_process

A parametrização do servidor fica por conta do processo de LED que inicializa o contexto com o buffer, seu tamanho, a porta onde vai servir e o callback preenchido, e assim passamos os argumentos para LED_Run iniciar o serviço.

A implementação no evento de recebimento da mensagem, compara a mensagem recebida com os comandos internos para o acionamento do LED, caso for igual executa a ação correspondente.

Compilando, Executando e Matando os processos

Para compilar e testar o projeto é necessário instalar a biblioteca de hardware necessária para resolver as dependências de configuração de GPIO da Raspberry Pi.

Compilando

Para facilitar a execução do exemplo, o exemplo proposto foi criado baseado em uma interface, onde é possível selecionar se usará o hardware da Raspberry Pi 3, ou se a interação com o exemplo vai ser através de input feito por FIFO e o output visualizado através de LOG.

Clonando o projeto

Pra obter uma cópia do projeto execute os comandos a seguir:

Selecionando o modo

Para selecionar o modo devemos passar para o cmake uma variável de ambiente chamada de ARCH, e pode-se passar os seguintes valores, PC ou RASPBERRY, para o caso de PC o exemplo terá sua interface preenchida com os sources presentes na pasta src/platform/pc, que permite a interação com o exemplo através de FIFO e LOG, caso seja RASPBERRY usará os GPIO’s descritos no artigo.

Modo PC

Modo RASPBERRY

Executando

Para executar a aplicação execute o processo launch_processes para lançar os processos button_process e led_process que foram determinados de acordo com o modo selecionado.

Uma vez executado podemos verificar se os processos estão rodando atráves do comando

O output

Interagindo com o exemplo

Dependendo do modo de compilação selecionado a interação com o exemplo acontece de forma diferente

MODO PC

Para o modo PC, precisamos abrir um terminal e monitorar os LOG’s

Dessa forma o terminal irá apresentar somente os LOG’s referente ao exemplo.

Para simular o botão, o processo em modo PC cria uma FIFO para permitir enviar comandos para a aplicação, dessa forma todas as vezes que for enviado o número 0 irá logar no terminal onde foi configurado para o monitoramento, segue o exemplo

Output do LOG quando enviado o comando algumas vezez

MODO RASPBERRY

Para o modo RASPBERRY a cada vez que o botão for pressionado irá alternar o estado do LED.

Monitorando o tráfego usando o tcpdump

Para monitorar as mensagens que trafegam, precisamos ler uma interface, para saber quais interfaces que o computador possui usamos o comando

Output

Como podemos ver temos 5 interfaces no computador onde o comando foi executado, pode ser que a máquina que esteja usando possa ter mais interfaces ou menos interfaces. Para teste local, iremos usar a interface local denominada lo, que representa a interface de loopback.

O tcpdump possui opções que permite a visualização dos dados, não irei explicar tudo, fica de estudo para quem quiser saber mais sobre a ferramenta. Executando o comando

Após executar o comando o tcpdump ficará fazendo sniffing da interface, tudo o que for trafegado nessa interface será apresentado, dessa forma enviamos um comando e veremos a seguinte saída:

Podemos ver que não há o processo de handshake somente o envio da mensagem, como descrito a seguir:

  • No instante 14:50:16.877785 IP 127.0.0.1.52661 > 127.0.0.1.1234 o cliente envia uma mensagem para o server

Testando conexão com o servidor via netcat

A aplicação realiza a comunicação entre processos locais, para testar uma comunicação remota usaremos o netcat que permite se conectar de forma prática ao servidor e enviar os comandos. Para se conectar basta usar o seguinte comando:

Como descrito no comando ip usaremos o ip apresentado na interface enp0s31f6 que é o IP 192.168.0.140, então o comando fica

E enviamos o comando LED ON, se visualizar no log irá apresentar que o comando foi executado, para monitorar com o tcpdump basta mudar a interface

Matando os processos

Para matar os processos criados execute o script kill_process.sh

Conclusão

O UDP é um protocolo utilizado quando se necessita enviar grandes volumes de dados, devido a sua simplicidade garante um maior desempenho em velocidade no envio de mensagens, o fato de não reportar as mensagens que recebeu, isso reduz a quantidade de processamento para tratar o protocolo, esse protocolo é usado também em aplicações de controle, estado e configuração da rede como o DNS(Domain Name System), o DHCP (Dynamic Host Configuration) e o RIP(Routing Information Protocol), nos próximo artigo iremos abordar o seu uso como Broadcast.

Referência

Outros artigos da série

<< TCP SSLUDP Broadcast >>
Notificações
Notificar
guest
0 Comentários
Inline Feedbacks
View all comments

WEBINAR

Visão Computacional para a redução de erros em processos manuais

DATA: 23/09 ÀS 17:00 H