sábado, 25 de fevereiro de 2012

Comunicação entre processos – PIPES em C++

Sistema operacional utilizado: Linux debian 2.6.32-5-amd64

O nosso caso de exemplo será com o que chamamos de "Named PIPES" também conhecidas como FIFO. Onde a utilizaremos para comunicação entre dois processos. Basicamente, teremos um servidor, que criará uma PIPE/FIFO onde o cliente escreverá um comando. O Servidor lerá essa mensagem, e escreverá na PIPE/FIFO do cliente a resposta do comando. Ou seja, o cliente também terá que criar sua PIPE para que o servidor possa escrever. Então, utilizaremos duas PIPES, uma criada pelo Server e a outra criada pelo cliente.

A Pipe criada pelo servidor deve ser conhecida antes de rodarmos o cliente. O cliente irá criar sua Pipe pelo PID do processo e irá transmitir o nome da Pipe para o servidor através da PIPE do server, assim o servidor será capaz de abrir a Pipe do cliente e escrever o resultado nela.

No nosso exemplo vamos usar três arquivos de codificação.

Main.cpp – Esse arquivo conterá a codificação para o nosso servidor
client.cpp – Esse arquivo conterá a codificação para o nosso cliente
header_fifo.h – Esse arquivo conterá a implementação de nossa classe.

Teremos tamém um quarto arquivo, um Makefile, que será responsável por compilar nosso exemplo.

Teremos duas opções de compilação, uma para Debug, onde você poderá ver as mensagens sendo trocadas entre cliente e servidor, o que será útil para modificar ou entender melhor o processo de comunicação.

Então vamos ao código de nosso servidor:


#include "fifo_header.h"

int main(int argc, char *argv[])
{
    int processID;
    if(argv[1]!=NULL)
    {
        if(strcmp("stop",argv[1]) ==0)
        {
                system("killall PipeServer");
                return 1;
        }
        else
        {
            if(strcmp("Debug",argv[1]) !=0)
            {

                processID = fork();

                if (processID < 0)
                {
                    exit(EXIT_FAILURE);
                }
                else
                {


                    if (processID > 0)
                    {
                        exit(EXIT_SUCCESS);
                    }
                }
            }
        }
    }

    FIFO myFifo, clientFifo;
    int PipeToWrite, PipeToRead, returnedValue, ready = 0;
    data buffer;
    myFifo.setName(argv[1]);
    myFifo.setPath(strcat(strcat(argv[2],"/"),argv[1]));
#ifdef DEBUG
    cout << "Name of FIFO: " << myFifo.getName() << endl;
    cout << "Path of FIFO: " <<myFifo.getPath() << endl;
    cout.flush();
#endif

    /*
    Vamos iniciar a criação de nossa PIPE
    */
    mknod(myFifo.getName(), S_IFIFO | 0666, 0);

    /*
    Vamos abrir a nossa PIPE que usaremos para ler do cliente
    */

    returnedValue = open(myFifo.getName(), O_RDONLY | O_NDELAY);
    if(returnedValue == -1)
    {
        printf("\n\tError in open PIPE\n");
        perror(myFifo.getMessage());
        return 1;
    }
    myFifo.pipeDescriptor = returnedValue;

    while (true)
    {

        returnedValue = myFifo.readFromPipe(clientFifo);

        if(returnedValue > 0)
        {
#ifdef DEBUG
        cout <<"retuned value from read: "<<returnedValue <<endl;
        cout <<"What Client said is: " <<myFifo.getMessage()<<endl;
#endif
        ready=0;
        do
        {
            returnedValue = open(clientFifo.getName(), O_WRONLY/*|O_NDELAY*/);
                if(returnedValue == -1)
                {
                    perror(clientFifo.getName());
                    return 1;
                }
#ifdef DEBUG
            cout <<"Pipe client Name: "<< clientFifo.getName() <<endl;
#endif
            clientFifo.pipeDescriptor = returnedValue;
            myFifo.writeToPipe(clientFifo);
            ready=1;


            }while(!ready);
            clientFifo.closePipe();
        }
    }

    return 1;
}

Agora o código de nosso cliente

#include "fifo_header.h"

int main(int argc, char *argv[])
{
    /*
    argv deve ser o mesmo do servidor.
    Assim termos como informar o nome da PIPE e o local onde o servidor
    a criou
    */
    FIFO myFifo, serverFifo;
    data buffer;
    int numberOfBytes, returnedValue, readOK;
    serverFifo.setName(argv[1]);
    serverFifo.setPath(strcat(strcat(argv[2],"/"),argv[1]));

#ifdef DEBUG
    cout << "Name of FIFO: " << myFifo.getName() << endl;
    cout << "Path of FIFO: " <<myFifo.getPath() << endl;
    cout.flush();
#endif
    sprintf(buffer.name, "/home/eduardo/Documents/programas/FIFO/%d", getpid( ));
    myFifo.setName(buffer.name);

    if (mknod(myFifo.getName(), S_IFIFO | 0666, 0) < 0)
    {
        perror(myFifo.getName());
        return 1;
    }
    if ((returnedValue = open(serverFifo.getName(), O_WRONLY)) == -1)
    {
        perror(serverFifo.getName());
        return 1;
    }
    serverFifo.pipeDescriptor = returnedValue;
    while(true)
    {

        cout << "\ncmd" <<endl;
#ifdef DEBUG
        cout <<"PIPE of server: " << serverFifo.getName() <<endl;
#endif
        numberOfBytes = read(fileno(stdin), buffer.message, PIPE_BUF);
#ifdef DEBUG
        cout <<"what you write: "<<buffer.message<<endl;
#endif
        if (!strncmp("exit", buffer.message, numberOfBytes - 1))
            break;

        write(serverFifo.pipeDescriptor,(char *) &buffer, sizeof(buffer));

        returnedValue = open(myFifo.getName(), O_RDONLY | O_NDELAY);

        if(returnedValue == -1)
        {
            perror(myFifo.getName());
            return 1;
        }
        myFifo.pipeDescriptor = returnedValue;
        readOK = 0;
        while (readOK != 1)
        {
            if((numberOfBytes = read(myFifo.pipeDescriptor, buffer.message, PIPE_BUF)) > 0)
            {
                write(fileno(stdout), buffer.message, numberOfBytes);
                memset(buffer.message,0x0,PIPE_BUF);
                readOK=1;
            }


        }
        myFifo.closePipe();


    }

    serverFifo.closePipe();
    return 1;
}


Agora a implementação de nossa classe


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <linux/limits.h>
#include <stdlib.h>
#include <iostream>
#include <cstdio>
#include <fstream>
#include <errno.h>
using namespace std;

typedef struct dataSet
{
        char name[255];
        char path[255];
        char message[PIPE_BUF];
}data;

class FIFO
{
    private:
        data info;
    public:
        int pipeDescriptor;
        char* getName();
        void setName(char*);
        char* getPath();
        void setPath(char*);
        char* getMessage();
        void writeMessage(char*);
        void closePipe();
        int writeToPipe(FIFO&);
        int readFromPipe(FIFO&);

};

char* FIFO::getName()
{
    return info.name;
}

void FIFO::setName(char * nameToSet)
{
    strcpy(info.name,nameToSet);
}

char* FIFO::getPath()
{
    return info.path;
}

void FIFO::setPath(char *pathToSet)
{
    strcpy(info.path, pathToSet);
}


char* FIFO::getMessage()
{
    return info.message;
}

void FIFO::writeMessage(char *messageToWrite)
{
   strcpy(info.message, messageToWrite);
}

int FIFO::writeToPipe(FIFO& pipeToWrite)
{
    FILE * result;
    int readFromFile, numberOfBytes, popenIsOk;
    char buffer[PIPE_BUF], stringBuff[PIPE_BUF];
#ifdef DEBUG
    cout <<"what will be the command to sheel: " << getMessage()<<endl;
#endif
    result = popen(getMessage(), "r");
    if(!result)
    {
#ifdef DEBUG
        cout <<"result is null" << endl;
        perror("errot in open file");
#endif
        strcpy(stringBuff,"Command not found");
        write(pipeToWrite.pipeDescriptor,stringBuff,strlen(stringBuff)+1);
        return -1;
    }


    popenIsOk=0;
    while(popenIsOk!=1)
    {
        if ((numberOfBytes = read(fileno(result), buffer, PIPE_BUF))>0)
        {
            strcpy(stringBuff,buffer);
            write(pipeToWrite.pipeDescriptor,stringBuff,strlen(stringBuff)+1);
            memset(buffer, 0x0, PIPE_BUF);
#ifdef DEBUG
            cout <<"what will be written to client: " << stringBuff << endl;
#endif
            popenIsOk=1;

        }
    }

    pclose(result);

}

int FIFO::readFromPipe(FIFO& pipe)
{

    int returnedValue;
    data buffer;
    returnedValue = read(pipeDescriptor, (char *) &buffer, sizeof(buffer));
    if(returnedValue <=0 )
        return returnedValue;
    else
    {
        writeMessage(buffer.message);
        pipe.setName(buffer.name);
    }
    return returnedValue;
}

void FIFO::closePipe()
{
    close(pipeDescriptor);
}

Agora vamos ao nosso Makefile


CC:=g++

all:PipeServer Client

PipeServer:main.cpp fifo_header.h
$(CC) -o PipeServer main.cpp

Client:client.cpp fifo_header.h
$(CC) -o Client client.cpp

Debug: main.cpp client.cpp fifo_header.h
$(CC) -D DEBUG -o PipeServer main.cpp
$(CC) -D DEBUG -o Client client.cpp

Comandos necessários para compilar e rodar:
Para compilar os dois programas em modo normal: 
Comando: make all

Para executar:
Comando para Rodar o Server: ./PipeServer FIFO /home/eduardo/Documents/programas/FIFO
Onde: 
  1. FIFO é o Nome da PIPE a ser Criada
  2.  /home/eduardo/Documents/programas/FIFO: Caminho da Pasta onde ela vai ser criada
Comando para Rodar o cliente: ./client FIFO /home/eduardo/Documents/programas/FIFO
  1. FIFO é o Nome da PIPE criada pelo server
  2.  /home/eduardo/Documents/programas/FIFO: Caminho da Pasta onde ela foi  criada
Como o Server roda em BackGround, se você desejar para o server dê o comando: ./PipeServer Stop
Para parar o cliente apenas digite exit

Para o dois programas em modo de DEBUG

comando para compilar: make Debug

Para rodar o server(agora ele não roda em BackGround Ctrl+C se desejar parar o server): ./PipeServer Debug /home/eduardo/Documents/programas/FIFO

Para rodar o cliente: ./client  Debug  /home/eduardo/Documents/programas/FIFO

Bons Testes!!!!