Quais os estados necessários para um servidor iniciar uma conexão TCP?

O protocolo TCP, como vimos nas lições anteriores, é um protocolo orientado a conexão. Isso significa que, antes que qualquer dado possa ser enviado entre dois hosts, uma conexão deve ser estabelecida entre eles primeiramente. Vamos estudar nessa lição o processo pela qual uma conexão TCP é estabelecida entre dois hosts em uma rede.

Estabelecendo uma conexão TCP

O processo de estabelecimento de conexões TCP se dá da seguinte forma:

  1. O host que quer iniciar a conexão, que normalmente é o cliente, envia um segmento com a flag SYN ativada, especificando a qual número de porta no servidor de destino ele quer se conectar, juntamente com o número de sequência inicial (Seq ISN).
  2. O servidor então responde com um segmento contendo a flag SYN, e seu número de sequência inicial. Além disso, o segmento SYN enviado pelo cliente é confirmado pela ativação da flag ACK neste segmento de resposta, confirmando o ISN do cliente + 1.
  3. O cliente então confirma o segmento SYN proveniente do servidor enviando outro segmento com a flag ACK ativada e o ISN do servidor + 1.

Após essa troca de três segmentos entre cliente e servidor, o processo de estabelecimento de conexão está completo. Chamamos esse processo de Handshake de três Vias ( Three-Way Handshake). Neste caso, o host que envia o primeiro segmento SYN realiza o que chamamos de uma abertura de conexão ativa. Já o outro host, em nosso exemplo o servidor que recebe o segmento SYN, realiza uma abertura de conexão passiva.

Cada vez que um dos hosts envia um segmento com a flag SYN para estabelecer a conexão, ele escolhe um número de sequência inicial para a conexão. Esse ISN muda ao longo do tempo, de modo que cada conexão terá um ISN diferente. De acordo com a RFC 793, o ISN deve ser visualizado como um contador de 32 bits que é incrementado em um a cada 4 μs (microsegundos). Desta forma, pacotes que sofrem um atraso muito grande na rede não serão entregues e, assim, evita-se que sejam interpretados erroneamente como pertencentes a outra conexão.

Veja na figura a seguir a captura dos pacotes trocados durante um handshake de três vias ao acessar um website a partir de um navegador:

Quais os estados necessários para um servidor iniciar uma conexão TCP?

Estabelecendo uma conexão TCP entre dois hosts

Meu host é o 192.168.1.119, e estou acessando o host 186.202.153.82. Note nas figuras as flags trocadas, e os números de sequência dos segmentos TCP.
Meu host envia um segmento com a flag SYN ativada, e número de sequência 0 (Seq=0); o servidor web remoto envia como resposta um segmento com as flags SYN e ACK ativas, número de sequência 0 (Seq=0) e confirmação do ISN igual a 0 + 1 =1 (Ack=1).
O cliente, então, finaliza o processo enviando outro segmento ao servidor, com a flag ACK ativa, número de sequência igual ISN a 1 (Seq=1) e confirmando o ISN do servidor (Ack=1).

A figura a seguir detalha o esquema de troca de segmentos entre os dois hosts considerados na captura anterior:

Quais os estados necessários para um servidor iniciar uma conexão TCP?

Handshake de Três Vias TCP

Apos o processo do handshake de três vias ter sido concluído, a conexão estará estabelecida e os hosts podem começar a trocar dados entre si. Ao final da comunicação, será necessário realizar um processo de finalização da conexão, que detalharemos na próxima lição.

Anterior: O Protocolo TCP

Próximo: Finalizando a conexão TCP entre dois hosts

  • Introdução
  • TCP
  • Conceito de servidor
  • Conceito de cliente
  • System Calls utilizados no TCP
  • Criando um socket Servidor
  • Criando um socket Cliente
  • Destruindo um socket Servidor/Cliente
  • Preparação do Ambiente
  • netcat
  • tcpdump
  • Implementação
    • Biblioteca
      • tcp_interface.h
      • tcp_server.h
      • tcp_server.c
      • tcp_client.h
      • tcp_client.c
    • launch_processes
    • button_interface
    • led_interface
    • button_process
    • led_process
  • Compilando, Executando e Matando os processos
  • Compilando
    • Clonando o projeto
    • Selecionando o modo
      • Modo PC
      • Modo RASPBERRY
  • Executando
  • Interagindo com o exemplo
    • MODO PC
    • MODO RASPBERRY
  • Monitorando o tráfego usando o tcpdump
  • Testando conexão com o servidor via netcat
  • Matando os processos
  • Conclusão
  • Referência

  • Comunicação entre processos
  • Biblioteca de Hardware
  • fork, exec, e daemon
  • Pipes
  • FIFO
  • Shared File
  • Signal – O que é e como usar?
  • MMAP
  • Socket TCP
  • TCP SSL
  • Socket UDP
  • UDP Broadcast
  • UDP Multicast
  • Socketpair
  • Queue System V
  • System V – Semaphore
  • Shared Memory System V
  • POSIX Queue
  • POSIX Semaphore
  • POSIX Shared Memory
  • D-Bus

Introdução

Até 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.

TCP

O 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 é considerado um protocolo confiável, pois provê garantia de entrega das mensagens, e de forma ordenada, roda sobre o protocolo IP, e sendo ele um protocolo orientado a conexão, necessita de uma troca de dados iniciais entre os envolvidos para estabelecer uma conexão, conhecido como handshake, o cliente envia um SYN para o servidor, então o servidor responde com um SYN ACK e por fim o cliente responde com um ACK.

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:

Quais os estados necessários para um servidor iniciar uma conexão TCP?

Normalmente a arquitetura mais empregada para esse protocolo é o Cliente/Servidor

Conceito de servidor

Pela 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 cliente

Cliente é 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 TCP

Para 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

#include <sys/types.h>

#include <sys/socket.h>

intsocket(intdomain,inttype,intprotocol);

Faz a junção da porta com o socket

#include <sys/types.h>

#include <sys/socket.h>

intbind(intsockfd,conststructsockaddr *addr,socklen_t addrlen);

Entra no modo de escuta, aguardando conexões

#include <sys/types.h>

#include <sys/socket.h>

intlisten(intsockfd,intbacklog);

Quando uma conexão é requisitada realiza a aceitação, estabelecendo a conexão

#include <sys/types.h>

#include <sys/socket.h>

intaccept(intsockfd,structsockaddr *addr,socklen_t *addrlen);

Estabelece uma comunicação

#include <sys/types.h>

#include <sys/socket.h>

intconnect(intsockfd,conststructsockaddr *addr,socklen_t addrlen);

Com a conexão estabelecida, permite o envio de mensagens para o endpoint receptor

#include <sys/types.h>

#include <sys/socket.h>

ssize_t send(intsockfd,constvoid*buf,size_t len,intflags);

Com a conexão estabelecida, permite receber mensagens do endpoint emissor

#include <sys/types.h>

#include <sys/socket.h>

ssize_t recv(intsockfd,void*buf,size_t len,intflags);

Solicita o fim de recepção de novas mensagens

#include <sys/socket.h>

intshutdown(intsockfd,inthow);

Destroí o socket

#include <unistd.h>

intclose(intfd);

Criando um socket Servidor

Para a criação de uma conexão para servidor é necessário seguir alguns passos:

  1. Criar um socket
  2. Realizar um bind com a porta especificada
  3. Iniciar a escuta de requisições de novas conexões
  4. Estabelecer a conexão
  5. Realizar a comunicação entre o servidor e o cliente

Criando um socket Cliente

Para a criação de uma conexão para cliente é necessário seguir alguns passos:

  1. Criar um socket
  2. Solicitar a conexão
  3. Realizar a comunicação entre o cliente e o servidor

Destruindo um socket Servidor/Cliente

  1. Interromper a troca de mensagens
  2. Realizar o fechamento do socket

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:

sudo apt-get install netcat

sudo apt-get install tcpdump

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 mensanges 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 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.
Para melhor isolar as implementações do servidor e do cliente foi criado uma biblioteca, que abstrai a rotina de inicialização e execução do servidor, e a rotina de conexão por parte do cliente.

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.

tcp_interface.h

Primeiramente criamos uma interface resposável por eventos de envio e recebimento, essa funções serão chamadas quando esses eventos ocorrerem.

typedefstruct

{

    int(*on_send)(char*buffer,int*size,void *user_data);  

    int(*on_receive)(char*buffer,intsize,void*user_data);

}TCP_Callback_t;

tcp_server.h

Criamos 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

typedefstruct

{

    intsocket;

    intport;

    char*buffer;

    int buffer_size;

    TCP_Callback_t cb;

}TCP_Server_t;

Essa função realiza os passos de 1 a 3 previamente descritos, para a inicilização do servidor

boolTCP_Server_Init(TCP_Server_t *server);

Essa função aguarda uma conexão e realiza a comunicação com o cliente.

boolTCP_Server_Exec(TCP_Server_t *server,void*data);

tcp_server.c

No 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.

boolstatus=false;

intis_valid;

intenable_reuse=1;

structsockaddr_in address;

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

if(!server||!server->buffer)

    break;

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.

server->socket=socket(AF_INET,SOCK_STREAM,0);

if(server->socket<0)

    break;

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

is_valid=setsockopt(server->socket,SOL_SOCKET,SO_REUSEADDR,(void*)&enable_reuse, sizeof(enable_reuse));

if(is_valid<0)

    break;

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

memset(&address,0,sizeof(address));

address.sin_family=AF_INET;

address.sin_addr.s_addr =htonl(INADDR_ANY);

address.sin_port=htons(server->port);

Aplicamos as configurações ao socket criado

is_valid=bind(server->socket,(structsockaddr *)&address,sizeof(address));

if(is_valid !=0)

    break;

Por fim colocamos o socket para escutar novas conexões

is_valid=listen(server->socket,1);

if(is_valid<0)

    break;

status= true;

Na função TCP_Server_Exec declaramos algumas variáveis para realizar a conexão e comunicação com o cliente

structsockaddr_in address;

socklen_t addr_len=sizeof(address);

intclient_socket;

size_t read_len;

intwrite_len;    

boolstatus=false;

Quando a conexão é solicitada por parte do cliente, o accept retorna o socket referente a conexão, caso for feita com sucesso

client_socket=accept(server->socket,(structsockaddr *)&address,&addr_len);

if(client_socket> 0)

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.

read_len=recv(client_socket,server->buffer,server->buffer_size,0);

if(server->cb.on_receive)

{

    server->cb.on_receive(server->buffer,read_len,data);

}

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.

if(server->cb.on_send)

{

    server->cb.on_send(server->buffer,&write_len,data);

    send(client_socket,server->buffer,(int)fmin(write_len,server->buffer_size),0);

}

status=true;

Interrompemos qualquer nova transação e fechamos o socket usado, concluindo a comunicação

shutdown(client_socket,SHUT_RDWR);

close(client_socket);

tcp_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 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

typedefstruct

{

    intsocket;

    constchar*hostname;

    intport;

    char *buffer;

    size_t buffer_size;

    TCP_Callback_t cb;

}TCP_Client_t;

Essa função realiza a conexão, envio e recebimento de mensagens para o servidor configurado

boolTCP_Client_Connect(TCP_Client_t *client,void*data);

tcp_client.c

Na 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.

boolstatus=false;

intis_valid;

structsockaddr_in server;

intsend_size;

intrecv_size;

Verificamos se o contexto e o buffer do cliente foram inicializados

if(!client||!client->buffer||client->buffer_size<=0)

    break;

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.

client->socket=socket(AF_INET,SOCK_STREAM,0);

if(client->socket<0)

    break;

Preenchemos a estrutura com o parâmetros pertinentes ao servidor

server.sin_family=AF_INET;

server.sin_port=htons(client->port);

Convertemos o hostname para o endereço relativo ao servidor

is_valid=inet_pton(AF_INET,client->hostname,&server.sin_addr);

if(is_valid<=0)

    break;

Solicitamos a conexão com o servidor previamente configurado, caso ocorra tudo de forma correta alteramos o status para verdadeiro

is_valid=connect(client->socket,(structsockaddr *)&server,sizeof(server));

if(is_valid <0)

    break;

status=true;

Aqui verificamos se a inicialização ocorreu com sucesso e se o callback para envio foi preenchido

if(status&&client->cb.on_send)

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

client->cb.on_send(client->buffer,&send_size,data);

send(client->socket,client->buffer, (int)fmin(send_size,client->buffer_size),0);

Se o callback para o recebimento foi preenchido passamos o contexto para a implementação do usuário tratar a resposta

if(client->cb.on_receive)

{

    recv_size=recv(client->socket,client->buffer, client->buffer_size,0);

    client->cb.on_receive(client->buffer,recv_size,data);

}

Por fim interrompemos qualquer nova transação e fechamos o socket e retornamos o status

shutdown(client->socket,SHUT_RDWR);

close(client->socket);

returnstatus;

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

  • launch_processes – é responsável por lançar os processos button_process e led_process atráves da combinação fork e exec
  • button_interface – é reponsável por ler o GPIO em modo de leitura da Raspberry Pi e se conectar ao servidor para enviar uma mensagem de alteração de estado.
  • led_interface – é reponsável por escutar novas conexões, recebendo comandos para 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.

intpid_button,pid_led;

intbutton_status,led_status;

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.

pid_button=fork();

if(pid_button==0)

{

    //start button process

    char *args[]={"./button_process",NULL};

    button_status=execvp(args[0],args);

    printf("Error to start button process, status = %d\n",button_status);

    abort();

}  

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

pid_led=fork();

if(pid_led==0)

{

    //Start led process

    char*args[] ={"./led_process",NULL};

    led_status=execvp(args[0],args);

    printf("Error to start led process, status = %d\n",led_status);

    abort();

}

button_interface

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

boolButton_Run(TCP_Client_t *client,Button_Data *button)

{

    staticintstate=0;

    if(button->interface->Init(button->object) ==false)

        returnfalse;

    while(true)

    {

        wait_press(button);

        state^=0x01;

        TCP_Client_Connect(client,&state);

    }

}

led_interface

A 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.

boolLED_Run(TCP_Server_t *server,LED_Data *led)

{

    if(led->interface->Init(led->object) ==false)

        returnfalse;

    TCP_Server_Init(server);

    while(true)

    {

        TCP_Server_Exec(server,led);

    }

    returnfalse;

}

button_process

Definimos uma lista de comandos que iremos enviar

constchar*states[]=

{

    "LED ON",

    "LED OFF"

};

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.

TCP_Client_t client=

{

    .buffer=client_buffer,

    .buffer_size=BUFFER_SIZE,

    .hostname ="127.0.0.1",

    .port=5555,

    .cb.on_send=on_send        

};

Button_Run(&client,&button);

A implementação no evento de envio, recuperamos o estado recebido e alteramos e indexamos com a lista de comando para enviar a mensagem

staticinton_send(char*buffer,int*size,void*data)

{

    int*state =(int*)data;

    snprintf(buffer,strlen(states[*state])+1, "%s",states[*state]);

    *size=strlen(states[*state])+1;

    return 0;

}

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 os callbacks preenchidos, nesse exemplo usaremos somente o de recebimento, e assim passamos os argumentos para LED_Run iniciar o serviço.

TCP_Server_t server=

    {

        .buffer=server_buffer,

        .buffer_size= sizeof(server_buffer),

        .port=5555,

        .cb.on_receive=on_receive_message

    };

    LED_Run(&server,&data);

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.

staticinton_receive_message(char*buffer,intsize,void*user_data)

{

    LED_Data *led =(LED_Data *)user_data;

    if(strncmp("LED ON",buffer,strlen("LED ON"))==0)

        led->interface->Set(led->object, 1);

    elseif(strncmp("LED OFF",buffer,strlen("LED OFF"))==0)

        led->interface->Set(led->object, 0);

    return0;

}

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 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 projeto

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

$git clonehttps://github.com/NakedSolidSnake/Raspberry_IPC_Socket_TCP

$cd Raspberry_IPC_Socket_TCP

$mkdir build&&cd build

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

$cmake-DARCH=PC..

$make

Modo RASPBERRY

$cmake-DARCH=RASPBERRY..

$make

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.

$cd bin

$./launch_processes

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

O output

cssouza  11492  1364  013:59pts/0    00:00:00./button_process

cssouza  11493  1364  0 13:59pts/0    00:00:00./led_process

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

$sudo tail-f/var/log/syslog|grep LED

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

Apr3014:01:12dell-cssouza LED TCP[11493]:LED Status:On

Apr30 14:01:13dell-cssouza LED TCP[11493]:LED Status:Off

Apr3014:01:13 dell-cssouza LED TCP[11493]:LED Status:On

Apr3014:01:14dell-cssouza LED TCP[11493]:LED Status:Off

Apr3014:01:14dell-cssouza LED TCP[11493]:LED Status:On

Apr3014:01:19dell-cssouza LED TCP[11493]:LED Status:Off

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

1:lo:<LOOPBACK,UP,LOWER_UP>mtu65536qdisc noqueue state UNKNOWN group defaultqlen1000

    link/loopback 00:00:00:00:00:00brd00:00:00:00:00:00

    inet127.0.0.1/8 scope host lo

       valid_lft forever preferred_lft forever

    inet6::1/128scope host

       valid_lft forever preferred_lft forever

2:enp0s31f6:<BROADCAST,MULTICAST,UP,LOWER_UP>mtu1500qdisc fq_codel state UP group default qlen1000

    link/ether10:65:30:22:8a:1abrd ff:ff:ff:ff:ff:ff

    inet10.0.0.100/24brd10.0.0.255scope globaldynamic noprefixroute enp0s31f6

       valid_lft17064secpreferred_lft17064sec

    inet6 fe80::3b0:2187:f4da:d8cd/64scope link noprefixroute

       valid_lft forever preferred_lft forever

3:wlp2s0:<NO-CARRIER,BROADCAST,MULTICAST,UP>mtu1500qdisc noqueue state DOWN group defaultqlen1000

    link/ether7c:2a:31:df:f0:02brd ff:ff:ff:ff:ff:ff

4:vboxnet0:<BROADCAST,MULTICAST,UP,LOWER_UP>mtu1500 qdisc fq_codel state UP group defaultqlen1000

    link/ether0a:00:27:00:00:00brd ff:ff:ff:ff:ff:ff

    inet172.16.11.100/24brd172.16.11.255scope globalvboxnet0

       valid_lft forever preferred_lft forever

    inet6 fe80::800:27ff:fe00:0/64scope link

       valid_lft forever preferred_lft forever

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:

sudo tcpdump-ilo-nnSX port5555

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:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

tcpdump:verbose output suppressed,use-vor-vv forfull protocol decode

listening on lo,link-type EN10MB(Ethernet),capture size262144bytes

14:03:45.435884IP127.0.0.1.44800>127.0.0.1.5555:Flags [S],seq2553339266,win65495,options[mss65495,sackOK,TS val3435397926ecr 0,nop,wscale7],length0

    0x0000:  4500003c11f5400040062ac5 7f000001  E..<..@.@.*.....

    0x0010:  7f000001af00 15b39830dd8200000000  .........0......

    0x0020:  a002 ffd7 fe30 00000204ffd70402080a  .....0..........

    0x0030:  ccc4032600000000 01030307            ...&........

14:03:45.435898IP127.0.0.1.5555> 127.0.0.1.44800:Flags[S.],seq463681437,ack2553339267,win65483,options[mss 65495,sackOK,TS val3435397926ecr3435397926,nop,wscale7],length0

    0x0000:  4500 003c0000400040063cba7f000001  E..<..@.@.<.....

    0x0010:  7f00 000115b3af001ba3379d9830dd83  ..........7..0..

    0x0020:  a012 ffcb fe3000000204ffd70402080a  .....0..........

    0x0030:  ccc40326ccc4 032601030307            ...&...&....

14:03:45.435911IP 127.0.0.1.44800>127.0.0.1.5555:Flags[.],ack463681438,win512,options[nop,nop,TS val3435397926ecr3435397926],length0

    0x0000:  4500003411f6400040062acc 7f000001  E..4..@.@.*.....

    0x0010:  7f000001af0015b3 9830dd831ba3379e  .........0....7.

    0x0020:  80100200fe280000 0101080accc40326  .....(.........&

    0x0030:  ccc4 0326                                ...&

14:03:45.435937IP127.0.0.1.44800> 127.0.0.1.5555:Flags[P.],seq2553339267:2553339275,ack463681438,win512,options [nop,nop,TS val3435397926ecr3435397926],length8

    0x0000:  4500003c11f7 400040062ac37f000001  E..<..@.@.*.....

    0x0010:  7f00 0001af0015b39830dd831ba3379e  .........0....7.

    0x0020:  8018 0200fe3000000101080accc40326  .....0.........&

    0x0030:  ccc40326 4c4544204f464600            ...&LED.OFF.

Podemos ver que há o processo de handshake seguido do envio da mensagem, como descritos a seguir:

  • No instante 14:03:45.435884 IP 127.0.0.1.44800 > 127.0.0.1.5555 o cliente envia uma SYN para o server
  • No instante 14:03:45.435898 IP 127.0.0.1.5555 > 127.0.0.1.44800 o servidor responde com um SYN ACK.
  • No instante 14:03:45.435911 IP 127.0.0.1.44800 > 127.0.0.1.5555 o cliente envia um ACK para o servidor.
  • E por fim, o cliente envia a mensagem podendo ser vista no fim da ultima mensagem LED.OFF.

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 processos

Para matar os processos criados execute o script kill_process.sh

$cd bin

$./kill_process.sh

Conclusão

Esse 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

  • Link do projeto completo
  • Linux Socket Programming by Example
  • The Linux Programming Interface: A Linux and UNIX System Programming Handbook
  • Mark Mitchell, Jeffrey Oldham, and Alex Samuel – Advanced Linux Programming
  • fork, exec e daemon
  • biblioteca hardware

Como se estabelece uma conexão TCP?

Para estabelecer uma conexão entre dispositivo e servidor, o protocolo TCP/IP usa um handshake de três vias. O dispositivo e o servidor devem sincronizar e reconhecer os pacotes antes do início da comunicação, então eles podem negociar, separar e transferir conexões.

Quais os principais protocolos que usam o TCP?

Protocolos de serviços básicos, que fornecem serviços para atender as próprias necessidades do sistema de comunicação TCP/IP: DNS, BOOTP, DHCP. Protocolos de serviços para o usuário: FTP, HTTP, Telnet, SMTP, POP3, IMAP, TFTP, NFS, NIS, LPR, LPD, ICQ, RealAudio, Gopher, Archie, Finger, SNMP e outros.

Quando iniciar uma conexão TCP Os computadores fazem um processo de sincronização Esse processo é chamado de?

O protocolo TCP O nome SYN,SYN-ACK,ACK é uma resumida descrição de como esse handshake funciona. A conexão entre dois hosts começa com o primeiro enviando ao segundo um pacote de sincronização (SYNchronize). O segundo host recebe esse pacote e responde com a confirmação do sincronização (SYNchronize-ACKnowledgment).

Qual é o tipo de conexão utilizada pelo protocolo TCP?

Transporte (Camada 3) Aqui encontramos o TCP, utilizado na conexão ponto-a-ponto. Sendo um protocolo de conexão mais confiável, ele é utilizado em aplicações que não possuem muita tolerância à perda de pacotes. Também encontramos o protocolo UDP (User Datagram Protocol), um protocolo com conexão não tão confiável.