Socketpair

Este post faz parte da série IPC(Inter process communication)

Introdução

Para que um processo se comunique com o outro de forma bidirecional é necessário ter duas instâncias de pipe para que o canal de escrita do pipe A se conecte ao canal de leitura do pipe B e vice e versa, em termos de código para realizar essa implementação é necessário criar duas instâncias de pipe e inserir como argumento ambos os handles para que a comunicação bidirecional seja possível. Existe uma alternativa para obter esse comportamento de forma simplificada.

socketpair

O socketpair é um systemcall capaz de conectar dois sockets de forma simplificada sem a necessidade de toda a inicialização de uma conexão tcp ou udp, devolvendo dois sockets conectados que podem ser usados para realizar a comunicação entre si de forma bidirecional.

Systemcalls usados no socketpair

#include <sys/types.h>
#include <sys/socket.h>

int socketpair(int domain, int type, int protocol, int sv[2]);

Dependendo do protocolo utilizado a forma de realizar a leitura e escrita do socket pode mudar. No caso de uso TCP usar as systemcalls pertinentes a TCP, o mesmo para UDP.

Implementação

Para demonstrar o uso desse IPC, vai ser utilizado o modelo Produtor/Consumidor, onde o processo Produtor(button_process) vai escrever seu estado interno no arquivo, e o Consumidor(led_process) vai ler o estado interno e vai aplicar o estado para si. Aplicação é composta por três executáveis sendo eles:

  • launch_processes – é responsável por criar os sockets e 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 é criado algumas variáveis iniciando com pair que corresponde aos sockets que vão ser criados, pid_button e pid_led que recebem o pid dos processos referentes à button_process e led_process, duas variáveis para armazenar o resultado caso o exec venha a falhar e um buffer para montar a lista de argumentos.

int pair[2];
int pid_button, pid_led;
int button_status, led_status;
char buffer[BUFFER_SIZE];

Aqui é criado o par de sockets usando o protocolo TCP

if(socketpair(AF_LOCAL, SOCK_STREAM, 0, pair) == -1)
  return EXIT_FAILURE;

Em seguida é criado um processo clone, se processo clone for igual a 0, os argumentos são preparados e armazenados na variável buffer e é criado 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
    snprintf(buffer, BUFFER_SIZE, "%d", pair[BUTTON_SOCKET]);
    char *args[] = {"./button_process", buffer, 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
    snprintf(buffer, BUFFER_SIZE, "%d", pair[LED_SOCKET]);
    char *args[] = {"./led_process", buffer, NULL};
    led_status = execvp(args[0], args);
    printf("Error to start led process, status = %d\n", led_status);
    abort();
}

E por fim o pair de sockets é fechado para o processo pai

close(pair[BUTTON_SOCKET]);
close(pair[LED_SOCKET]);

button_interface

A implementação da interface de botão é relativamente simples, onde o botão é iniciado e em seguida fica em loop aguardando um evento de pressionamento de botão, que quando pressionado altera o estado da variável interna e envia para o socket usando o handle e a mensagem formatada.

bool Button_Run(void *object, int handle, Button_Interface *button)
{
    char buffer[BUFFER_LEN];
    int state = 0;

    if(button->Init(object) == false)
        return false;        
   
    while(true)
    {
        wait_press(object, button);

        state ^= 0x01;
        memset(buffer, 0, BUFFER_LEN);
        snprintf(buffer, BUFFER_LEN, "%d", state);
        send(handle, buffer, strlen(buffer), 0);        
    }

    close(handle);
    
    return false;
}

led_interface

A implementação da interface de LED é relativamente simples, onde o LED é iniciado e em seguida fica em loop aguardando uma mensagem, que quando recebida altera o estado do LED com o valor lido.

bool LED_Run(void *object, int handle, LED_Interface *led)
{
	char buffer[BUFFER_LEN];
	int state;

	if(led->Init(object) == false)
		return false;
	
	while(true)
	{
		recv(handle, buffer, BUFFER_LEN, 0);
		sscanf(buffer, "%d", &state);
		led->Set(object, state);
	}

	close(handle);
	
	return false;	
}

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 clone https://github.com/NakedSolidSnake/Raspberry_IPC_TCP_Socketpair
$ cd Raspberry_IPC_TCP_Socketpair
$ 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

$ ps -ef | grep _process

O output

pi        2773     1  0 10:25 pts/0    00:00:00 led_process 4
pi        2774     1  1 10:25 pts/0    00:00:00 button_process 3

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

$ 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

echo "0" > /tmp/socketpair_fifo

Output

Apr 27 11:09:54 cssouza-Latitude-5490 LED SOCKETPAIR[9883]: LED Status: On
Apr 27 11:09:55 cssouza-Latitude-5490 LED SOCKETPAIR[9883]: LED Status: Off
Apr 27 11:09:55 cssouza-Latitude-5490 LED SOCKETPAIR[9883]: LED Status: On
Apr 27 11:09:55 cssouza-Latitude-5490 LED SOCKETPAIR[9883]: LED Status: Off
Apr 27 11:09:56 cssouza-Latitude-5490 LED SOCKETPAIR[9883]: LED Status: On
Apr 27 11:09:56 cssouza-Latitude-5490 LED SOCKETPAIR[9883]: LED Status: Off

MODO RASPBERRY

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

Matando os processos

Para matar os processos criados execute o script kill_process.sh

$ cd bin
$ ./kill_process.sh

Conclusão

O socketpair é uma boa alternativa para o uso de pipes pois facilita o modo de comunicação entre processos caso haja necessidade que os processos se comuniquem entre si, ou seja, de forma bidirecional. Devido a sua facilidade de criação do par de sockets, isso o torna relativamente fácil de usar. Portanto caso houver a necessidade de usar pipes sempre prefira o socketpair por garantir a comunicação entre ambas as direções.

Referência

IPC(Inter process communication)

UDP Multicast Queue System V
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
0 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Home » Linux Embarcado » Socketpair

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: