简体   繁体   中英

Writing and reading messages continuously in ipc with pipes in C

I have written a program where the parent process creates two child processes. The parent process writes to either the first or the second child and the child reads the message.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <sys/wait.h>

#define MSGSIZE 64

char msgbuf[MSGSIZE];

int main(){

    int p1[2];
    int p2[2];
    int nread;
    int choice = 0;
    pid_t child_a,child_b;


    if(pipe(p1) == -1){
        printf("error in creating pipe\n");
        exit(-1);
    }

    if(pipe(p2) == -1){
        printf("error in creating pipe\n");
        exit(-1);
    }

    child_a = fork();

if (child_a == 0) {
    dup2(p1[0], STDIN_FILENO);
    read(STDIN_FILENO,msgbuf,MSGSIZE); 
    printf("%d receives message: %s\n",getpid(),msgbuf);
    close(p1[0]); 
    close(p1[1]);
} else {
    child_b = fork();

    if (child_b == 0) {
        dup2(p2[0], STDIN_FILENO); 
        read(STDIN_FILENO,msgbuf,MSGSIZE);
        printf("%d receives message: %s\n",getpid(),msgbuf);
        close(p2[0]); 
        close(p2[1]); 
    } else {
        /* Parent Code */
        // Write something to child A
        while(1){
        printf("<child_to_receive_msg> <message>\n");
        scanf("%d %s",&choice,msgbuf);
        switch(choice){
        case 1:
            usleep(250);
            write(p1[1], msgbuf, MSGSIZE);
            break;
        // Write something to child B
        case 2:
            usleep(250);
            write(p2[1], msgbuf, MSGSIZE);
            break;
        case -1: 
            usleep(250);
            printf("parent waiting");
            wait(NULL);
            exit(-1);
            break;
        }
       }
      }
    }

    return 0;
}

My issue is that I want the parent to keep writing to the child process. With the above code, once it writes to child or child 2 it wont write again or at least the child process wont read it again. I don't know if it is possible to do this.

I tried putting the while loop at the beginning of the program but this causes another child process to be created every time.

Here my solution, followed by some explanation:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <assert.h>

#define MSGSIZE 64

void read_process(int fh) {

        assert(-1 < fh);

        char msgbuf[MSGSIZE];

        ssize_t retval = 0;

        while (-1 < retval) {
                retval = read(fh, msgbuf, MSGSIZE);
                printf("%d receives message: %s\n", getpid(), msgbuf);
                close(fh);
        }

}

void write_process(int fh_child_1, int fh_child_2) {

        assert(-1 < fh_child_1);
        assert(-1 < fh_child_2);

        char msgbuf[MSGSIZE];
        int choice = -1;

        while (1) {
                printf("<child_to_receive_msg> <message>\n");

                scanf("%d %64s", &choice, msgbuf);

                switch (choice) {
                        case 1:
                                write(fh_child_1, msgbuf, MSGSIZE);
                                break;
                        // Write something to child B
                        case 2:
                                write(fh_child_2, msgbuf, MSGSIZE);
                                break;
                        case -1:
                                printf("parent waiting");
                                wait(NULL);
                                exit(-1);
                                break;
                }
        }
}

int main() {

        /* 0 will be for reading, 1 for writing */
        int p1[2];
        int p2[2];

        pid_t pid;

        if (pipe(p1) == -1) {
                printf("error in creating pipe\n");
                exit(-1);
        }

        /* Don't create 2nd pipe yet, we don't require it here and save us to
         * tear it down in child 1 */

        pid = fork();

        if (pid == 0) {
                close(p1[1]); /* not needed here */
                p1[1] = -1;
                read_process(p1[0]);
                exit(EXIT_SUCCESS);

        } else {
                /* Nobody is going to read from pipe 1 here again */
                close(p1[0]);
                p1[0] = -1; /* Mark fh as invalid */

                if (pipe(p2) == -1) {
                        printf("error in creating pipe\n");
                        exit(-1);
                }

                pid = fork();

                if (pid == 0) {
                        close(p1[1]);
                        p1[1] = -1;

                        close(p2[1]);
                        p2[1] = -1;

                        /* Ensure we did not forget an fh */
                        assert(-1 == p1[0]);
                        assert(-1 == p1[1]);
                        assert(-1 == p2[1]);

                        read_process(p2[0]);
                        exit(EXIT_SUCCESS);

                } else {
                        /* Parent Code */

                        close(p2[0]);
                        p2[0] = -1;

                        assert(-1 == p1[0]);
                        assert(-1 == p2[0]);

                        write_process(p1[1], p2[1]);
                        exit(EXIT_SUCCESS);
                }
        }

        return 0;
}

As noted before, your main problem was that your children processes don't loop but read just once.

There are a few notes, I will first talk about the general ones, and then the more system programming specific ones afterwards.

Your main problem, the missing loops, was hidden below your code being a bit spaghetti.

C provides means for structuring your code, like functions. Functions are not only there for re-using code, but can also be used to summarize your code: Instead of pasting the code for your child processes directly where you require it, shift the code to a dedicated function and just call the function. This aids greatly in understanding the basic structure of the code:

close(p1[1]); /* not needed here */
p1[1] = -1;

read_process(p1[0]);
exit(EXIT_SUCCESS);

is pretty obvious what is the basic idea, isn't it? And if you want the gory details of the read process, inspect the read_process function.

Be careful about your resources. Allocate as few as possible, only when you need them. Free them as soon as possible and mark them as freed - see the pipe file handles.

Lastly, if you fork, the process is basically copied. You create a second process, and it is given copies / clones of all (well, most) of the parent processes resources.

The file handles in your case, are cloned. Eg closing p1[0] in your child does not affect p1[0] in your parent, because they are not the same. That also means, that immediately after forking, you should consider all the resources available and get rid of every resource you wont require immediately, like

pid = fork();
if(0 == pid) {
    close(p1[1]); /* not needed here */
    p1[1] = -1;
    read_process(p1[0]);
    exit(EXIT_SUCCESS);
}

Your first child does not require p1[1] , thus close it and mark it as closed and invalid.

There is probably much more to say, but these are the points that come to my mind immediately.

Some of it might not seem clumsy, but as you get more and more experienced, and your code bases grow, you will appreciate these things, at least I do more and more every day. As for the code, there are certainly many bugs still hiding in there, you get the basic idea though I hope ;)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM