Proper fork() and pipe() use for a single parent having multiple children. How do I do this right?

爱⌒轻易说出口 提交于 2019-12-05 22:52:25

The basic problem you have is that you depend on EOFs to detect things, but an EOF won't occur until ALL handles on the write end of the pipe are closed. So you have to be careful about closing ALL unneeded handles in all processes.

In your code you have a loop that creates pipes, then forks:

  • first you create two pipes for child 0
  • then fork child 0
  • child 0 closes parent ends of those pipes, while parent closes child ends of those pipes (good)
  • loop
  • create two pipes for child 1
  • fork child 1
  • child 1 closes parent ends of its pipes, while parent closes child ends.

At this point, you have a problem -- child 1 has inherited the parent ends of the pipe to child 0, but isn't closing them. Which means that child 0 won't be able to detect an EOF when it reads from parent. The same thing happens with child 2 and later children.

This kind of looks like a code-only answer, but it's not really, since the comments in the code explain what's going on. It isn't the same game you're writing, but shows the mechanism you're looking for. You can adapt this kind of framework to your specific situation.

Code:

/*  Demonstration of multiplayer "game" with processes.
 *
 *  The parent sets up a number of processes equal to NUM_KIDS.
 *  It loops through each one in turn, and writes a character to
 *  each child, beginning with one. The child reads it, and if
 *  that character is the winning number, it writes back to the
 *  parent to notify it, and exits. If it's not the winning
 *  character, it writes a different character to the parent (which
 *  is ignored) and waits for another character to read. If it
 *  reads the game over character, it exits. 
 *
 *  It's not a very fun game, but demonstrates how a number of
 *  child processes can act as different players, how they can
 *  receive input from the parent and, based on that input, how
 *  they can determine a win situation and notify the parent of
 *  such.
 */


#define _POSIX_C_SOURCE 200809L

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


#define NUM_KIDS 5

static const int CHILD_NO_WIN = 0;  /*  Child sends this if it doesnt win    */
static const int CHILD_WIN = 1;     /*  Child sends this if it wins          */
static const int GAME_OVER = 0;     /*  Child loses if it receives this      */
static const int WINNER = 13;       /*  Child wins if it receives this       */


/*  Convenience function to make a pair of pipes  */

void make_pipe_pair(int * pair1, int * pair2)
{
    if ( pipe(pair1) == -1 || pipe(pair2) == -1 ) {
        perror("couldn't create pipe");
        exit(EXIT_FAILURE);
    }
}


/*  Convenience function to close a pair of file descriptors  */

void close_pair(const int rfd, const int wfd)
{
    if ( close(rfd) == -1 || close(wfd) == -1 ) {
        perror("couldn't close file");
        exit(EXIT_FAILURE);
    }
}


/*  Main child process function  */

void child_func(const int rpipe, const int wpipe, const size_t child_id)
{
    char out_c = CHILD_NO_WIN;      /*  Character to write  */
    char in_c;                      /*  Character to read   */
    bool keep_reading = true;

    while ( keep_reading ) {

        /*  Read a single character from the parent  */

        ssize_t num_read;
        if ( (num_read = read(rpipe, &in_c, 1)) == -1 ) {
            perror("error reading from pipe in child");
            exit(EXIT_FAILURE);
        }
        else if ( num_read == 0 ) {
            printf("Pipe from parent closed to child %zu.\n", child_id);
            keep_reading = false;
        }
        else {
            printf("Child %zu read %d from parent.\n", child_id, in_c);

            if ( in_c == GAME_OVER ) {

                /*  We lost, so tell loop to end. No need to write()
                 *  to parent, since it already knows a previous
                 *  child won.                                        */

                printf("Child %zu got game over signal.\n", child_id);
                keep_reading = false;
            }
            else {
                if ( in_c == WINNER ) {

                    /*  We won, so send won signal to parent  */

                    out_c = 1;
                }

                /*  Write won signal to parent if we won, or
                 *  other character if we didn't.             */

                if ( write(wpipe, &out_c, 1) == -1 ) {
                    perror("error writing to pipe in child");
                    exit(EXIT_FAILURE);
                }
                else {
                    printf("Child %zu wrote %d to parent.\n", child_id, out_c);
                }
            }
        }
    }


    /*  Close file descriptors and exit  */

    close_pair(rpipe, wpipe);
}


/*  Main function  */

int main(void)
{
    int ptoc_fd[NUM_KIDS][2];   /*  Parent to child pipes    */
    int ctop_fd[NUM_KIDS][2];   /*  Child to parent pipes    */
    pid_t children[NUM_KIDS];   /*  Process IDs of children  */
    int winning_child;          /*  Holds number of winner   */


    /*  Create pipe pairs and fork children  */

    for ( size_t i = 0; i < NUM_KIDS; ++i ) {
        make_pipe_pair(ptoc_fd[i], ctop_fd[i]);

        if ( (children[i] = fork()) == -1 ) {
            perror("error calling fork()");
            return EXIT_FAILURE;
        }
        else if ( children[i] == 0 ) {
            printf("Child %zu created.\n", i + 1);
            close_pair(ctop_fd[i][0], ptoc_fd[i][1]);
            child_func(ptoc_fd[i][0], ctop_fd[i][1], i + 1);
            printf("Child %zu terminating.\n", i + 1);
            return EXIT_SUCCESS;
        }
        else {
            close_pair(ptoc_fd[i][0], ctop_fd[i][1]);
        }
    }


    /*  Set up game variables and enter main loop  */

    char out_c = 1;
    char in_c = 0;
    bool won = false;

    while ( !won ) {

        /*  Loop through each child  */

        for ( size_t i = 0; !won && i < NUM_KIDS; ++i ) {

            /*  Write next number to child  */

            if ( write(ptoc_fd[i][1], &out_c, 1) == -1 ) {
                perror("error writing to pipe");
                exit(EXIT_FAILURE);
            }
            else {
                printf("Parent wrote %d to child %zu.\n", out_c, i+1);
            }

            ++out_c;


            /*  Read status from child if game not over  */

            if ( !won ) {
                ssize_t num_read;
                if ( (num_read = read(ctop_fd[i][0], &in_c, 1)) == -1 ) {
                    perror("error reading from pipe");
                    return EXIT_FAILURE;
                }
                else if ( num_read == 0 ) {
                    printf("Pipe from child %zu closed.\n", i+1);
                }
                else {
                    printf("Parent read %d from child %zu.\n", in_c, i+1);
                    if ( in_c == CHILD_WIN ) {
                        printf("Parent got won signal from child %zu.\n", i+1);
                        won = true;
                        winning_child = i+1;
                    }
                }
            }
        }
    }


    /*  Clean up and harvest dead children  */

    out_c = 0;
    for ( size_t i = 0; i < NUM_KIDS; ++i ) {
        if ( write(ptoc_fd[i][1], &out_c, 1) == -1 ) {
            perror("error writing to pipe");
            exit(EXIT_FAILURE);
        }
        else {
            printf("Parent wrote %d to child %zu.\n", out_c, i + 1);
        }

        if ( waitpid(children[i], NULL, 0) == -1 ) {
            perror("error calling waitpid()");
            return EXIT_FAILURE;
        }
        else {
            printf("Successfully waited for child %zu.\n", i + 1);
        }

        close_pair(ptoc_fd[i][1], ctop_fd[i][0]);
    }


    /*  Show who won, and then quit.  */

    printf("Parent terminating. Child %d won.\n", winning_child);

    return EXIT_SUCCESS;
}

and output:

paul@thoth:~/src/sandbox/multipipe$ ./multipipe
Child 1 created.
Child 1 read 1 from parent.
Parent wrote 1 to child 1.
Child 1 wrote 0 to parent.
Child 3 created.
Parent read 0 from child 1.
Parent wrote 2 to child 2.
Child 2 created.
Child 2 read 2 from parent.
Parent read 0 from child 2.
Parent wrote 3 to child 3.
Child 3 read 3 from parent.
Parent read 0 from child 3.
Child 4 created.
Parent wrote 4 to child 4.
Child 3 wrote 0 to parent.
Child 2 wrote 0 to parent.
Child 4 read 4 from parent.
Child 5 created.
Parent read 0 from child 4.
Parent wrote 5 to child 5.
Child 4 wrote 0 to parent.
Child 5 read 5 from parent.
Parent read 0 from child 5.
Parent wrote 6 to child 1.
Child 5 wrote 0 to parent.
Child 1 read 6 from parent.
Parent read 0 from child 1.
Parent wrote 7 to child 2.
Child 1 wrote 0 to parent.
Child 2 read 7 from parent.
Parent read 0 from child 2.
Parent wrote 8 to child 3.
Child 3 read 8 from parent.
Parent read 0 from child 3.
Child 2 wrote 0 to parent.
Parent wrote 9 to child 4.
Child 4 read 9 from parent.
Parent read 0 from child 4.
Parent wrote 10 to child 5.
Child 3 wrote 0 to parent.
Child 4 wrote 0 to parent.
Child 5 read 10 from parent.
Child 5 wrote 0 to parent.
Parent read 0 from child 5.
Parent wrote 11 to child 1.
Child 1 read 11 from parent.
Parent read 0 from child 1.
Parent wrote 12 to child 2.
Child 2 read 12 from parent.
Child 1 wrote 0 to parent.
Parent read 0 from child 2.
Parent wrote 13 to child 3.
Child 3 read 13 from parent.
Parent read 1 from child 3.
Parent got won signal from child 3.
Parent wrote 0 to child 1.
Child 2 wrote 0 to parent.
Child 1 read 0 from parent.
Child 1 got game over signal.
Child 1 terminating.
Child 3 wrote 1 to parent.
Successfully waited for child 1.
Parent wrote 0 to child 2.
Child 2 read 0 from parent.
Child 2 got game over signal.
Child 2 terminating.
Successfully waited for child 2.
Parent wrote 0 to child 3.
Child 3 read 0 from parent.
Child 3 got game over signal.
Child 3 terminating.
Successfully waited for child 3.
Parent wrote 0 to child 4.
Child 4 read 0 from parent.
Child 4 got game over signal.
Child 4 terminating.
Successfully waited for child 4.
Parent wrote 0 to child 5.
Child 5 read 0 from parent.
Child 5 got game over signal.
Child 5 terminating.
Successfully waited for child 5.
Parent terminating. Child 3 won.
paul@thoth:~/src/sandbox/multipipe$

The output looks a little weird in place, and looks like processes are reading stuff before it's been written, but that's what happens when you work with asynchronous processes and don't bother to synchronize your input/output for demo purposes. The actual reading and writing to the pipes is well behaved and synchronized, it's just the debug messages going to standard output that look screwy. You should still be able to see what's going on.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!