Minh Pham
Minh Pham

Reputation: 304

How to build a chat program with pipe in C++

I would like to implement a small program that runs either chatter 1 or chatter 2, depending the argument input when calling main. Upon opening 2 terminals, and chatter 1, chatter 2 up, I expect to send small string messages between the 2 terminals.

I try to implement a simple pipe program, in which a process will open a pipe as either read or write. Then the process will close the respective end, read end or write end if the it doesn't you the pipe for the other purpose. As I open 2 terminals, and call main with argument 0 or 1, I call 2 processes to open the pipes.

However, I have trouble with running the program to continuously send messages between 2 process. Whenever a keyboard input is detect, the process exits.

I suspect that I didn't open the pipe correctly. (There is no open function in my code). The open function also requires me to put in the pathname of my pipe, which is in /proc/[PID]/fd. I see that PID will be different everytime I run the program. So, how can I put it into open function's argument.

Please help me to revise my code.

#include <unistd.h>   /* to use pipe, getpid*/
#include <stdio.h>    /*to use prinf*/
#include <iostream>   /*to use cin, cout*/
#include <string.h>   /*to use str function*/
#include <errno.h>    /* to use with errno*/

int onServiceStart(int chatter, int readPipe[], int writePipe[]){
    if( close(readPipe[1]) == -1 ){   //if use the pipe to read, then close the write end of the pipe
        std::cout<<"Error closing the write end of read pipe"<<errno<<std::endl;
    };
    if( close(writePipe[0]) == -1 ){   //if use the pipe to write, then close the read end of the pipe
        std::cout<<"Error closing the read end of write pipe"<<errno<<std::endl;
    };
    while(true){
        //preparing to get the string from keyboard
        std::string chatInput;
        getline(std::cin, chatInput);
        //send the string to pipe
        if ( write( writePipe[1],chatInput.c_str(),strlen( chatInput.c_str() )) == -1) {
            std::cout<<"Error writing to write end of write pipe"<<errno<<std::endl;
        };
        //preparing the buffer to put the string to after reading from pipe
        char buffer;
        if ( read(readPipe[0],&buffer,1)== -1) {
            std::cout<<"Error reading from read end of read pipe"<<errno<<std::endl;
        };
        std::cout<<buffer;
    }
};

int main(int argc, char *argv[]){
    int chatter = *(argv[1]) - '0';
    int fileDescription1[2], fileDescription2[2] ;
    if ( pipe(fileDescription1)==-1 ) {
        printf("cannot create pipe 1");
    };
    if ( pipe(fileDescription2)==-1 ) {
        printf("cannot create pipe 2");
    };

    switch (chatter) {
        case 0:
            printf("PID %d, chatter 1: \n", getpid());
            onServiceStart(chatter,
                            fileDescription1,       //chatter1 will use fileDescription1 to read,
                            fileDescription2);     //chatter1 will use fileDescription2 to write,

        case 1:
            printf("PID %d, chatter 2: \n", getpid());
            onServiceStart(chatter,
                            fileDescription2,       //chatter2 will use fileDescription2 to read,
                            fileDescription1);    //chatter2 will use fileDescription1 to write,
    }
}

Upvotes: 1

Views: 1161

Answers (2)

Minh Pham
Minh Pham

Reputation: 304

I switch to use named pipe instead. Unnamed pipe cannot be used for communication between 2 independent processes, but only parent and child process.

Since pipe is uni-direction, I have to use 2 pipes. For each process, the pipe will be used as either read only, or write only. For example, process 0 only open pipe 1 as read only only, will only call read() function to read pipe.

Since it is named pipe, I have to close and delete the pipe manually. In this code, I haven't implement that yet. It will need a signal catching mechanism to delete the pipe upon closing or "Crtl+C" the program. There is also a small bug that will cause if you run main 1 before main 0. Nevertheless, this small code below demo the basis of pipe.

Here is my code.

#include <unistd.h>    /* to use pipe, getpid */
#include <sys/stat.h>  /* to use named pipe mkfifo */
#include <sys/types.h> /* to use open() */
#include <fcntl.h>     /* to use open() */
#include <stdio.h>     /*to use prinf */
#include <iostream>    /*to use cin, cout */
#include <string.h>    /*to use str function */
#include <errno.h>     /* to use with errno */
#include <thread>      /* to use thread */

#define PIPE1_PATH "/home/phongdang/pipe1"
#define PIPE2_PATH "/home/phongdang/pipe2"

int openPipe(const char* pipePathName, int flag){
    int fileDescriptor;
    fileDescriptor = open(pipePathName, flag) ;
    if ( fileDescriptor == -1) {
        std::cout<<"Error open pipe at "<<pipePathName<<" .Error code: "<<errno<<std::endl;
    };
    return fileDescriptor;
}

void writePipe(int writePipeDescriptor){
    while (true){
        //preparing to get the string from keyboard
        std::string chatInput;
        getline(std::cin, chatInput);
        int writeBytes = write( writePipeDescriptor,chatInput.c_str(),strlen( chatInput.c_str() ));
        //send the string to pipe
        if ( writeBytes == -1) {
        std::cout<<"Error writing to write end of write pipe"<<errno<<std::endl;
        }
        else {
            printf("Writing to pipe %d bytes \n", writeBytes);
        }
        sleep(1);
    }
}

void readPipe(int readPipeDescriptor){
        char buffer[100];
    while (true){
        //preparing the buffer to put the string to after reading from pipe
memset(buffer, '\0', 100);
       // memset(buffer, 0, 10);
        int readByte = read(readPipeDescriptor,&buffer,10);
        if ( readByte== -1) {
            std::cout<<"Error reading from read end of read pipe"<<errno<<std::endl;
        }
        else std::cout<<"Read "<<readByte<<" bytes from pipe :"<<buffer<<std::endl;
        sleep(1);
    }
}

int main(int argc, char *argv[]){
    int chatter = *(argv[1]) - '0';
    int writePipeDescriptor, readFileDescriptor;
    switch (chatter) {
        case 0:
        {
            printf("PID %d, chatter 1: \n", getpid());
            //create pipe is done by chatter 0 only (this is just a hot fix to prevent error 17 EEXIST)
            if ( mkfifo(PIPE1_PATH, S_IRUSR | S_IWUSR | S_IWGRP ) ==-1 ) {  //create pipe for read/write/execute by owner, and others
                std::cout<<("cannot create pipe 1 \n")<<errno<<std::endl;
            };
            writePipeDescriptor = openPipe(PIPE1_PATH, O_WRONLY);
            readFileDescriptor  = openPipe(PIPE2_PATH, O_RDONLY);
            std::thread readThread(readPipe,readFileDescriptor);    //has to create thread and execute thread first.
            writePipe(writePipeDescriptor);
            readThread.join();
            break;
        }
        case 1:
        {
            printf("PID %d, chatter 2: \n", getpid());

            if ( mkfifo(PIPE2_PATH, S_IRUSR | S_IWUSR | S_IWGRP ) ==-1 ) {           //create pipe for read/write/execute by owner, and others
                std::cout<<("cannot create pipe 2 \n")<<errno<<std::endl;
            };
            readFileDescriptor = openPipe(PIPE1_PATH,  O_RDONLY);
            writePipeDescriptor = openPipe(PIPE2_PATH, O_WRONLY);
            std::thread writeThread(writePipe,writePipeDescriptor);
            readPipe(readFileDescriptor);
            writeThread.join();
            break;
        }
    }
    return 0;
}

Upvotes: 0

G. Sliepen
G. Sliepen

Reputation: 7973

There are several issues. The reason why the process immediately exits is because your program only runs one side of the pipes, not the other side. If you run it with chatter = 0 for example, you use fileDescription2 for the writePipe, but the first thing you do in onServiceStart() is to close() the reading side of writePipe. This means that whenever you write to writePipe[1], you will get an EPIPE error because writePipe[0] is closed.

If the intention is to start two instances of your program, one called with 0 as the argument and the other with 1, then you need to use a pair of named pipes to communicate between them, although if you want bi-directional communication a named UNIX socket is even better, since you only need one.

Note that a pair of anonymous pipes created with pipe() is only useful if your program forks or creates multiple threads.

Another issue is that you are not reading the whole response from the other end of the pipe:

//preparing the buffer to put the string to after reading from pipe
char buffer;
if ( read(readPipe[0],&buffer,1)== -1) {
    ...

This only reads one character. After that, you start reading from standard input again, so you will only receive one character for each line you send, which is clearly not what you want.

The proper way to deal with this is to make the pipes non-blocking by passing the O_NONBLOCK flag, and use a select() or poll() loop to wait for data from both standard input and the pipe simultaneously.

Upvotes: 1

Related Questions