Show
IntroduçãoAté o momento nos referimos a IPC’s que permite a comunicação entre processos em uma mesma máquina, o que não é o caso de socket. Sockets é um tipo de IPC muito especial, para não dizer o melhor, esse IPC permite a comunicação entre dois processos na mesma máquina, bem como a comunicação entre dois processos em máquinas diferentes através de uma rede, ou seja, dois processos rodando em computadores fisicamente separados. Um socket é um dispositivo de comunicação bidirecional, sendo possível enviar e receber mensagens. Para entender de forma fácil o que seria o socket, é fazer uma analogia com uma ligação telefônica, você deseja ligar para uma determinado número, então disca esse número e quando a conexão é estabelecida começa a tocar no telefone na outra ponta, a pessoa atende e começam a conversar, o mesmo ocorre para o socket, porém ao invés de usar número usamos IP e porta para estabelecer a comunicação. Este IPC possui duas formas de comunicação(não irei mencionar as outras devido não serem tão utilizadas) conhecidas como TCP(Transmission Control Protocol) nesse caso as mensagens são enviadas na forma de stream de mensagens, e UDP(User Datagram Protocol) onde as mensagens são enviadas em blocos de mensagens. Nesse artigo iremos abordar o TCP. TCPO Assunto sobre TCP é imenso, por isso iremos nos limitar somente ao funcionamento, que é na sua aplicação, caso queria saber mais como o protocolo funciona, nas referências consta a bibliografia utilizada.
O TCP permite conexões entre processos em máquinas distintas, dessa forma podemos atribuir funções para cada uma dessas máquinas, caracterizando uma aplicação distribuída, onde cada máquina possui uma responsabilidade dentro da aplicação. A figura abaixo demonstra a conexão entre duas máquinas:
Normalmente a arquitetura mais empregada para esse protocolo é o Cliente/Servidor Conceito de servidorPela definição do dicionário servidor é um computador que disponibiliza informação e serviços a outros computadores ligados em rede, dessa forma sempre deve estar disponível, para que quando desejado o acesso a ele sempre seja possível. Conceito de clienteCliente é um computador que consome os serviços e informações de um servidor, podendo ser interno ou pela rede de computadores System Calls utilizados no TCPPara criar uma aplicação utilizando TCP 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
Entra no modo de escuta, aguardando conexões
Quando uma conexão é requisitada realiza a aceitação, estabelecendo a conexão
Estabelece uma comunicação
Com a conexão estabelecida, permite o envio de mensagens para o endpoint receptor
Com a conexão estabelecida, permite receber mensagens do endpoint emissor
Solicita o fim de recepção de novas mensagens
Destroí o socket
Criando um socket ServidorPara a criação de uma conexão para servidor é necessário seguir alguns passos:
Criando um socket ClientePara a criação de uma conexão para cliente é necessário seguir alguns passos:
Destruindo um socket Servidor/Cliente
Preparação do AmbienteAntes 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:
netcatO netcat é uma ferramenta capaz de interagir com conexões UDP e TCP, podendo abrir conexões, ouvindo como um servidor, ou com cliente enviando mensanges para um servidor. tcpdumpO 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çãoPara demonstrar o uso desse IPC, iremos utilizar o modelo Cliente/Servidor, onde o processo Cliente(button_process) vai enviar uma mensagem com comandos pré-determinados para o servidor, e o Servidor(led_process) vai ler as mensagens e verificar se possui o comando cadastrado, assim o executando. BibliotecaA biblioteca criada permite uma fácil criação do servidor, sendo o servidor orientado a eventos, ou seja, fica aguardando as mensagens chegarem. tcp_interface.hPrimeiramente criamos uma interface resposável por eventos de envio e recebimento, essa funções serão chamadas quando esses eventos ocorrerem.
tcp_server.hCriamos também um contexto que armazena os paramêtros 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 a interface das funções de callback
Essa função realiza os passos de 1 a 3 previamente descritos, para a inicilização do servidor
Essa função aguarda uma conexão e realiza a comunicação com o cliente.
tcp_server.cNo TCP_Server_Init definimos algumas variáveis 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 que recebe o resultado das funções necessárias para a configuração, 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 e o buffer foi inicializado, que é de reponsabilidade do usuário
Criamos um endpoint com o perfil de se conectar via protocolo IPv4(AF_INET), do tipo stream que caracteriza o TCP(SOCK_STREAM), o último parâmetro pode ser 0 nesse caso.
Aqui permitimos o reuso do socket caso necessite reiniciar o serviço
Preenchemos a estrutura com parâmetros fornecidos pelo usuário como em qual porta que o serviço vai rodar.
Aplicamos as configurações ao socket criado
Por fim colocamos o socket para escutar novas conexões
Na função TCP_Server_Exec declaramos algumas variáveis para realizar a conexão e comunicação com o cliente
Quando a conexão é solicitada por parte do cliente, o accept retorna o socket referente a conexão, caso for feita com sucesso
O Servidor aguarda a troca de mensagem, assim que receber realiza a verificação se o callback para recebimento foi preenchido caso sim, passa o conteúdo para o callback realizar o tratamento.
Aqui é verificado se o callback para envio foi configurado, dessa forma o buffer é passado para que a implementação prepare a mensagem a ser enviada, e alteramos o status para true, indicando que a comunicação foi feita com sucesso.
Interrompemos qualquer nova transação e fechamos o socket usado, concluindo a comunicação
tcp_client.hCriamos 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 ser conectar, port que recebe o número que corresponde qual o serviço deseja consumir, buffer que aponta para a memória alocada previamente pelo usuário, buffer_size o representa o tamanho do buffer e a interface das funções de callback
Essa função realiza a conexão, envio e recebimento de mensagens para o servidor configurado
tcp_client.cNa função TCP_Client_Connect definimos algumas variáveis para auxiliar na comunicação com o servidor, sendo uma variável booleana que representa o estado da parametrização do cliente, uma variável do tipo inteiro que recebe o resultado das funções necessárias para a configuração, uma estrutura sockaddr_in que é usada para configurar o servidor no qual será conectado, e duas variáveis de quantidade de dados enviados e recebidos.
Verificamos se o contexto e o buffer do cliente foram inicializados
Criamos um endpoint com o perfil de se conectar via protocolo IPv4(AF_INET), do tipo stream que caracteriza o TCP(SOCK_STREAM), o último parâmetro pode ser 0 nesse caso.
Preenchemos a estrutura com o parâmetros pertinentes ao servidor
Convertemos o hostname para o endereço relativo ao servidor
Solicitamos a conexão com o servidor previamente configurado, caso ocorra tudo de forma correta alteramos o status para verdadeiro
Aqui verificamos se a inicialização ocorreu com sucesso e se o callback para envio foi preenchido
Em caso de sucesso passamos o contexto para a implementação feita pelo usuário para preparar o dados a ser enviado para o servidor
Se o callback para o recebimento foi preenchido passamos o contexto para a implementação do usuário tratar a resposta
Por fim interrompemos qualquer nova transação e fechamos o socket e retornamos o status
A aplicação é composta por três executáveis sendo eles:
launch_processesNo 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_interfaceA 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_interfaceA implementação do LED_Run ficou simples também, onde realizamos a inicialização da interface de LED, do servidor e ficamos em loop aguardando o recebimento de uma conexão.
button_processDefinimos uma lista de comandos que iremos enviar
A parametrização do cliente fica por conta do processo de botão que inicializa o contexto com o buffer, seu tamanho, o endereço do hostname, o serviço que deseja consumir e os callbacks preenchidos, nesse exemplo usaremos somente o de envio, não estando interessado na recepção, e assim passamos os argumentos para Button_Run iniciar o processo.
A implementação no evento de envio, recuperamos o estado recebido e alteramos e indexamos com a lista de comando para enviar a mensagem
led_processA 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 os callbacks preenchidos, nesse exemplo usaremos somente o de recebimento, 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 processosPara 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. CompilandoPara faciliar 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 projetoPra obter uma cópia do projeto execute os comandos a seguir:
Selecionando o modoPara 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
ExecutandoPara 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
Dependendo do modo de compilação selecionado a interação com o exemplo acontece de forma diferente MODO PCPara 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 RASPBERRYPara o modo RASPBERRY a cada vez que o botão for pressionado irá alternar o estado do LED. Monitorando o tráfego usando o tcpdumpPara 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 4 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 há o processo de handshake seguido do envio da mensagem, como descritos a seguir:
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 10.0.0.100, 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 processosPara matar os processos criados execute o script kill_process.sh
ConclusãoEsse sem dúvida é o melhor IPC, pois permite a comunicação entre processos na mesma máquina e em máquinas fisicamente separadas, também é possível se comunicar com outras tecnologias baseado em um protocolo padrão, além disso, permite outras utilidades como comunicação entre threads para evitar concorrências, criação de padrões arquiteturais como cliente/servidor utilizado nessa aplicação, bem como a criação da biblioteca de comunicação conhecida como zeromq. Porém com toda essa facilidade, gera-se um grande problema quando precisa-se trafegar os dados em uma rede pública, quando feito dessa forma os dados estão sendo expostos como visto no tcpdump, mas existe uma forma de protegê-los, assunto para o próximo artigo. Referência
Como é identificado um socket TCP?Na rede, a representação de um socket se dá por ip:porta , por exemplo: 127.0.0.1:4477 (IPv4). Um socket que usa rede é um Socket TCP/IP.
Quais os estados necessários para um servidor iniciar uma conexão TCP?Estados iniciais da conexão TCP (flags SYN, SYN-ACK, ACK).. LISTEN: o servidor esta aguardando uma solicitação para iniciar uma conexão.. SYN-SENT: um cliente envia uma mensagem SYN para o servidor e aguarda a resposta SYN-ACK.. SYN-RCVD: o servidor recebeu a mensagem SYN do cliente e retornou uma mensagem SYN–ACK.. O que é socket e seu funcionamento?O que são sockets? Os sockets podem ser definidos como um processo de comunicação que permite dois diferentes processos de conversarem e trocarem informação entre si. Na internet, por exemplo, eles funcionam para gerar uma conexão entre usuário e site.
Quais são os quatro elementos que identificam um socket TCP?Endereço IP de origem, endereço IP de destino, número da porta de origem e número da porta de destino.
|